Merge mozilla-inbound to mozilla-central. r=merge a=merge
authorNARCIS BELEUZU <nbeleuzu@mozilla.com>
Fri, 03 Nov 2017 12:04:55 +0200
changeset 443245 4e6df5159df3e64c3c453c0db0a2e1d126819b71
parent 443219 e6d86b7284bae701700b9d300ee1476ebe5f3eed (current diff)
parent 443244 14a5ae97c682ed92861492a1e51f041b6d6cef40 (diff)
child 443246 83fe273bfdf2c388359f7dfe66e1dedb09ebf53f
child 443283 b2375c59ecd9768a469ce59bc3cf84819dc14faa
child 443297 c17190dfd88b8427b19349e32f689567bad6e079
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central. r=merge a=merge
--- a/accessible/ipc/win/HandlerProvider.cpp
+++ b/accessible/ipc/win/HandlerProvider.cpp
@@ -4,18 +4,20 @@
  * 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/. */
 
 #define INITGUID
 
 #include "mozilla/a11y/HandlerProvider.h"
 
 #include "Accessible2_3.h"
+#include "AccessibleDocument.h"
 #include "AccessibleTable.h"
 #include "AccessibleTable2.h"
+#include "AccessibleTableCell.h"
 #include "HandlerData.h"
 #include "HandlerData_i.c"
 #include "mozilla/Assertions.h"
 #include "mozilla/a11y/AccessibleWrap.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/Move.h"
 #include "mozilla/mscom/AgileReference.h"
 #include "mozilla/mscom/FastMarshaler.h"
@@ -335,18 +337,30 @@ HandlerProvider::MarshalAs(REFIID aIid)
   // Otherwise we juse return the identity.
   return aIid;
 }
 
 REFIID
 HandlerProvider::GetEffectiveOutParamIid(REFIID aCallIid,
                                          ULONG aCallMethod)
 {
-  if (aCallIid == IID_IAccessibleTable || aCallIid == IID_IAccessibleTable2) {
-    return IID_IAccessible2_3;
+  if (aCallIid == IID_IAccessibleTable ||
+      aCallIid == IID_IAccessibleTable2 ||
+      aCallIid == IID_IAccessibleDocument ||
+      aCallIid == IID_IAccessibleTableCell ||
+      aCallIid == IID_IAccessibleRelation) {
+    return NEWEST_IA2_IID;
+  }
+
+  // IAccessible2_2::accessibleWithCaret
+  static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3,
+                "You have modified NEWEST_IA2_IID. This code needs updating.");
+  if ((aCallIid == IID_IAccessible2_2 || aCallIid == IID_IAccessible2_3) &&
+      aCallMethod == 47) {
+    return NEWEST_IA2_IID;
   }
 
   MOZ_ASSERT(false);
   return IID_IUnknown;
 }
 
 HRESULT
 HandlerProvider::NewInstance(REFIID aIid,
--- a/accessible/ipc/win/PlatformChild.cpp
+++ b/accessible/ipc/win/PlatformChild.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/mscom/InterceptorLog.h"
 
 #include "Accessible2.h"
 #include "Accessible2_2.h"
 #include "AccessibleHypertext2.h"
 #include "AccessibleTable2.h"
 #include "AccessibleTableCell.h"
 
+#include "AccessibleDocument_i.c"
 #include "AccessibleHypertext2_i.c"
 
 namespace mozilla {
 namespace a11y {
 
 /**
  * Unfortunately the COM interceptor does not intrinsically handle array
  * outparams. Instead we manually define the relevant metadata here, and
--- a/config/check_spidermonkey_style.py
+++ b/config/check_spidermonkey_style.py
@@ -88,16 +88,17 @@ included_inclnames_to_ignore = set([
     'unicode/uchar.h',          # ICU
     'unicode/uclean.h',         # ICU
     'unicode/ucol.h',           # ICU
     'unicode/udat.h',           # ICU
     'unicode/udatpg.h',         # ICU
     'unicode/udisplaycontext.h',# ICU
     'unicode/uenum.h',          # ICU
     'unicode/uloc.h',           # ICU
+    'unicode/unistr.h',         # ICU
     'unicode/unorm2.h',         # ICU
     'unicode/unum.h',           # ICU
     'unicode/unumsys.h',        # ICU
     'unicode/upluralrules.h',   # ICU
     'unicode/ureldatefmt.h',    # ICU
     'unicode/ustring.h',        # ICU
     'unicode/utypes.h',         # ICU
     'vtune/VTuneWrapper.h'      # VTune
--- a/docshell/base/moz.build
+++ b/docshell/base/moz.build
@@ -92,23 +92,25 @@ UNIFIED_SOURCES += [
     'nsContextMenuInfo.cpp',
     'nsDefaultURIFixup.cpp',
     'nsDocShell.cpp',
     'nsDocShellEditorData.cpp',
     'nsDocShellEnumerator.cpp',
     'nsDocShellLoadInfo.cpp',
     'nsDocShellTransferableHooks.cpp',
     'nsDocShellTreeOwner.cpp',
-    'nsDownloadHistory.cpp',
     'nsDSURIContentListener.cpp',
     'nsWebNavigationInfo.cpp',
     'PendingGlobalHistoryEntry.cpp',
     'SerializedLoadContext.cpp',
 ]
 
+if not CONFIG['MOZ_PLACES']:
+    UNIFIED_SOURCES += ['nsDownloadHistory.cpp']
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/docshell/shistory',
     '/dom/base',
     '/layout/base',
     '/layout/generic',
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -31,18 +31,20 @@
 #endif
 
 // session history
 #include "nsSHEntry.h"
 #include "nsSHEntryShared.h"
 #include "nsSHistory.h"
 #include "nsSHTransaction.h"
 
+#ifndef MOZ_PLACES
 // download history
 #include "nsDownloadHistory.h"
+#endif
 
 
 // LoadContexts (used for testing)
 #include "LoadContext.h"
 
 using mozilla::dom::ContentHandlerService;
 
 static bool gInitialized = false;
@@ -95,18 +97,20 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsExterna
 #endif
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(ContentHandlerService, Init)
 
 // session history
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHEntry)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHTransaction)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHistory)
 
+#ifndef MOZ_PLACES
 // download history
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloadHistory)
+#endif
 
 NS_DEFINE_NAMED_CID(NS_DOCSHELL_CID);
 NS_DEFINE_NAMED_CID(NS_DEFAULTURIFIXUP_CID);
 NS_DEFINE_NAMED_CID(NS_WEBNAVIGATION_INFO_CID);
 NS_DEFINE_NAMED_CID(NS_ABOUT_REDIRECTOR_MODULE_CID);
 NS_DEFINE_NAMED_CID(NS_URI_LOADER_CID);
 NS_DEFINE_NAMED_CID(NS_DOCUMENTLOADER_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_EXTERNALHELPERAPPSERVICE_CID);
@@ -121,17 +125,19 @@ NS_DEFINE_NAMED_CID(NS_DBUSHANDLERAPP_CI
 #if defined(MOZ_WIDGET_ANDROID)
 NS_DEFINE_NAMED_CID(NS_EXTERNALSHARINGAPPSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_EXTERNALURLHANDLERSERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_SHENTRY_CID);
 NS_DEFINE_NAMED_CID(NS_SHTRANSACTION_CID);
 NS_DEFINE_NAMED_CID(NS_SHISTORY_CID);
 NS_DEFINE_NAMED_CID(NS_SHISTORY_INTERNAL_CID);
+#ifndef MOZ_PLACES
 NS_DEFINE_NAMED_CID(NS_DOWNLOADHISTORY_CID);
+#endif
 NS_DEFINE_NAMED_CID(NS_CONTENTHANDLERSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_LOADCONTEXT_CID);
 NS_DEFINE_NAMED_CID(NS_PRIVATELOADCONTEXT_CID);
 
 const mozilla::Module::CIDEntry kDocShellCIDs[] = {
   { &kNS_DOCSHELL_CID, false, nullptr, nsDocShellConstructor },
   { &kNS_DEFAULTURIFIXUP_CID, false, nullptr, nsDefaultURIFixupConstructor },
   { &kNS_WEBNAVIGATION_INFO_CID, false, nullptr, nsWebNavigationInfoConstructor },
@@ -152,17 +158,19 @@ const mozilla::Module::CIDEntry kDocShel
 #if defined(MOZ_WIDGET_ANDROID)
   { &kNS_EXTERNALSHARINGAPPSERVICE_CID, false, nullptr, nsExternalSharingAppServiceConstructor },
   { &kNS_EXTERNALURLHANDLERSERVICE_CID, false, nullptr, nsExternalURLHandlerServiceConstructor },
 #endif
   { &kNS_SHENTRY_CID, false, nullptr, nsSHEntryConstructor },
   { &kNS_SHTRANSACTION_CID, false, nullptr, nsSHTransactionConstructor },
   { &kNS_SHISTORY_CID, false, nullptr, nsSHistoryConstructor },
   { &kNS_SHISTORY_INTERNAL_CID, false, nullptr, nsSHistoryConstructor },
+#ifndef MOZ_PLACES
   { &kNS_DOWNLOADHISTORY_CID, false, nullptr, nsDownloadHistoryConstructor },
+#endif
   { &kNS_LOADCONTEXT_CID, false, nullptr, mozilla::CreateTestLoadContext },
   { &kNS_PRIVATELOADCONTEXT_CID, false, nullptr, mozilla::CreatePrivateTestLoadContext },
   { nullptr }
 };
 
 const mozilla::Module::ContractIDEntry kDocShellContracts[] = {
   { "@mozilla.org/docshell;1", &kNS_DOCSHELL_CID },
   { NS_URIFIXUP_CONTRACTID, &kNS_DEFAULTURIFIXUP_CID },
@@ -213,17 +221,19 @@ const mozilla::Module::ContractIDEntry k
 #if defined(MOZ_WIDGET_ANDROID)
   { NS_EXTERNALSHARINGAPPSERVICE_CONTRACTID, &kNS_EXTERNALSHARINGAPPSERVICE_CID },
   { NS_EXTERNALURLHANDLERSERVICE_CONTRACTID, &kNS_EXTERNALURLHANDLERSERVICE_CID },
 #endif
   { NS_SHENTRY_CONTRACTID, &kNS_SHENTRY_CID },
   { NS_SHTRANSACTION_CONTRACTID, &kNS_SHTRANSACTION_CID },
   { NS_SHISTORY_CONTRACTID, &kNS_SHISTORY_CID },
   { NS_SHISTORY_INTERNAL_CONTRACTID, &kNS_SHISTORY_INTERNAL_CID },
+#ifndef MOZ_PLACES
   { NS_DOWNLOADHISTORY_CONTRACTID, &kNS_DOWNLOADHISTORY_CID },
+#endif
   { NS_LOADCONTEXT_CONTRACTID, &kNS_LOADCONTEXT_CID },
   { NS_PRIVATELOADCONTEXT_CONTRACTID, &kNS_PRIVATELOADCONTEXT_CID },
   { nullptr }
 };
 
 static const mozilla::Module kDocShellModule = {
   mozilla::Module::kVersion,
   kDocShellCIDs,
--- a/dom/file/ipc/IPCBlobInputStream.cpp
+++ b/dom/file/ipc/IPCBlobInputStream.cpp
@@ -154,17 +154,17 @@ IPCBlobInputStream::~IPCBlobInputStream(
   Close();
 }
 
 // nsIInputStream interface
 
 NS_IMETHODIMP
 IPCBlobInputStream::Available(uint64_t* aLength)
 {
-  // We don't have a remoteStream yet. Let's return the full known size.
+  // We don't have a remoteStream yet: let's return 0.
   if (mState == eInit || mState == ePending) {
     *aLength = 0;
     return NS_OK;
   }
 
   if (mState == eRunning) {
     MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream);
 
--- a/gfx/2d/RecordedEventImpl.h
+++ b/gfx/2d/RecordedEventImpl.h
@@ -1072,17 +1072,17 @@ private:
 
 class RecordedScaledFontCreationByIndex : public RecordedEventDerived<RecordedScaledFontCreationByIndex> {
 public:
 
   static void FontInstanceDataProc(const uint8_t* aData, uint32_t aSize,
                                    const FontVariation* aVariations, uint32_t aNumVariations,
                                    void* aBaton)
   {
-    auto recordedScaledFontCreation = static_cast<RecordedScaledFontCreation*>(aBaton);
+    auto recordedScaledFontCreation = static_cast<RecordedScaledFontCreationByIndex*>(aBaton);
     recordedScaledFontCreation->SetFontInstanceData(aData, aSize, aVariations, aNumVariations);
   }
 
   RecordedScaledFontCreationByIndex(ScaledFont* aScaledFont, size_t aUnscaledFontIndex)
     : RecordedEventDerived(SCALEDFONTCREATIONBYINDEX)
     , mRefPtr(aScaledFont)
     , mUnscaledFontIndex(aUnscaledFontIndex)
     , mGlyphSize(aScaledFont->GetSize())
@@ -1093,17 +1093,18 @@ public:
   virtual bool PlayEvent(Translator *aTranslator) const;
 
   template<class S> void Record(S &aStream) const;
   virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const;
 
   virtual std::string GetName() const { return "ScaledFont Creation"; }
   virtual ReferencePtr GetObjectRef() const { return mRefPtr; }
 
-  void SetFontInstanceData(const uint8_t *aData, uint32_t aSize);
+  void SetFontInstanceData(const uint8_t *aData, uint32_t aSize,
+                           const FontVariation* aVariations, uint32_t aNumVariations);
 
 private:
   friend class RecordedEvent;
 
   ReferencePtr mRefPtr;
   size_t mUnscaledFontIndex;
   Float mGlyphSize;
   std::vector<uint8_t> mInstanceData;
@@ -3033,19 +3034,21 @@ RecordedScaledFontCreationByIndex::Recor
 
 inline void
 RecordedScaledFontCreationByIndex::OutputSimpleEventInfo(std::stringstream &aStringStream) const
 {
   aStringStream << "[" << mRefPtr << "] ScaledFont Created By Index";
 }
 
 inline void
-RecordedScaledFontCreationByIndex::SetFontInstanceData(const uint8_t *aData, uint32_t aSize)
+RecordedScaledFontCreationByIndex::SetFontInstanceData(const uint8_t *aData, uint32_t aSize,
+                                                const FontVariation* aVariations, uint32_t aNumVariations)
 {
   mInstanceData.assign(aData, aData + aSize);
+  mVariations.assign(aVariations, aVariations + aNumVariations);
 }
 
 template<class S>
 RecordedScaledFontCreationByIndex::RecordedScaledFontCreationByIndex(S &aStream)
   : RecordedEventDerived(SCALEDFONTCREATIONBYINDEX)
 {
   ReadElement(aStream, mRefPtr);
   ReadElement(aStream, mUnscaledFontIndex);
--- a/gfx/layers/PaintThread.cpp
+++ b/gfx/layers/PaintThread.cpp
@@ -16,16 +16,68 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/SyncRunnable.h"
 
 namespace mozilla {
 namespace layers {
 
 using namespace gfx;
 
+bool
+CapturedBufferState::Copy::CopyBuffer()
+{
+  if (mSource->Lock(OpenMode::OPEN_READ_ONLY)) {
+    mDestination->UpdateDestinationFrom(*mSource, mBounds);
+    mSource->Unlock();
+    return true;
+  }
+  return false;
+}
+
+bool
+CapturedBufferState::Unrotate::UnrotateBuffer()
+{
+  return mBuffer->UnrotateBufferTo(mParameters);
+}
+
+bool
+CapturedBufferState::PrepareBuffer()
+{
+  return (!mBufferCopy || mBufferCopy->CopyBuffer()) &&
+         (!mBufferUnrotate || mBufferUnrotate->UnrotateBuffer());
+}
+
+void
+CapturedBufferState::GetTextureClients(nsTArray<RefPtr<TextureClient>>& aTextureClients)
+{
+  if (mBufferCopy) {
+    if (TextureClient* source = mBufferCopy->mSource->GetClient()) {
+      aTextureClients.AppendElement(source);
+    }
+    if (TextureClient* sourceOnWhite = mBufferCopy->mSource->GetClientOnWhite()) {
+      aTextureClients.AppendElement(sourceOnWhite);
+    }
+    if (TextureClient* destination = mBufferCopy->mDestination->GetClient()) {
+      aTextureClients.AppendElement(destination);
+    }
+    if (TextureClient* destinationOnWhite = mBufferCopy->mDestination->GetClientOnWhite()) {
+      aTextureClients.AppendElement(destinationOnWhite);
+    }
+  }
+
+  if (mBufferUnrotate) {
+    if (TextureClient* client = mBufferUnrotate->mBuffer->GetClient()) {
+      aTextureClients.AppendElement(client);
+    }
+    if (TextureClient* clientOnWhite = mBufferUnrotate->mBuffer->GetClientOnWhite()) {
+      aTextureClients.AppendElement(clientOnWhite);
+    }
+  }
+}
+
 StaticAutoPtr<PaintThread> PaintThread::sSingleton;
 StaticRefPtr<nsIThread> PaintThread::sThread;
 PlatformThreadId PaintThread::sThreadId;
 
 // RAII make sure we clean up and restore our draw targets
 // when we paint async.
 struct MOZ_STACK_CLASS AutoCapturedPaintSetup
 {
@@ -154,36 +206,80 @@ void
 PaintThread::BeginLayerTransaction()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   MOZ_ASSERT(!mInAsyncPaintGroup);
 }
 
 void
+PaintThread::PrepareBuffer(CapturedBufferState* aState)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aState);
+
+  // If painting asynchronously, we need to acquire the compositor bridge which
+  // owns the underlying MessageChannel. Otherwise we leave it null and use
+  // synchronous dispatch.
+  RefPtr<CompositorBridgeChild> cbc;
+  if (!gfxPrefs::LayersOMTPForceSync()) {
+    cbc = CompositorBridgeChild::Get();
+    cbc->NotifyBeginAsyncPrepareBuffer(aState);
+  }
+  RefPtr<CapturedBufferState> state(aState);
+
+  RefPtr<PaintThread> self = this;
+  RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::PrepareBuffer",
+    [self, cbc, state]() -> void
+  {
+    self->AsyncPrepareBuffer(cbc,
+                             state);
+  });
+
+  if (cbc) {
+    sThread->Dispatch(task.forget());
+  } else {
+    SyncRunnable::DispatchToThread(sThread, task);
+  }
+}
+
+void
+PaintThread::AsyncPrepareBuffer(CompositorBridgeChild* aBridge,
+                                CapturedBufferState* aState)
+{
+  MOZ_ASSERT(IsOnPaintThread());
+  MOZ_ASSERT(aState);
+
+  if (!aState->PrepareBuffer()) {
+    gfxCriticalNote << "Failed to prepare buffers on the paint thread.";
+  }
+
+  aBridge->NotifyFinishedAsyncPrepareBuffer(aState);
+}
+
+void
 PaintThread::PaintContents(CapturedPaintState* aState,
                            PrepDrawTargetForPaintingCallback aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aState);
 
   // If painting asynchronously, we need to acquire the compositor bridge which
   // owns the underlying MessageChannel. Otherwise we leave it null and use
   // synchronous dispatch.
   RefPtr<CompositorBridgeChild> cbc;
   if (!gfxPrefs::LayersOMTPForceSync()) {
     cbc = CompositorBridgeChild::Get();
     cbc->NotifyBeginAsyncPaint(aState);
   }
   RefPtr<CapturedPaintState> state(aState);
-  RefPtr<DrawTargetCapture> capture(aState->mCapture);
 
   RefPtr<PaintThread> self = this;
   RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::PaintContents",
-    [self, cbc, capture, state, aCallback]() -> void
+    [self, cbc, state, aCallback]() -> void
   {
     self->AsyncPaintContents(cbc,
                              state,
                              aCallback);
   });
 
   if (cbc) {
     sThread->Dispatch(task.forget());
--- a/gfx/layers/PaintThread.h
+++ b/gfx/layers/PaintThread.h
@@ -7,16 +7,17 @@
 #ifndef MOZILLA_LAYERS_PAINTTHREAD_H
 #define MOZILLA_LAYERS_PAINTTHREAD_H
 
 #include "base/platform_thread.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/layers/TextureClient.h"
+#include "RotatedBuffer.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace gfx {
 class DrawTarget;
 class DrawTargetCapture;
 };
 
@@ -53,16 +54,66 @@ public:
   gfx::Matrix mTargetTransform;
   SurfaceMode mSurfaceMode;
   gfxContentType mContentType;
 
 protected:
   virtual ~CapturedPaintState() {}
 };
 
+// Holds the key operations for a ContentClient to prepare
+// its buffers for painting
+class CapturedBufferState final {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CapturedBufferState)
+public:
+  struct Copy {
+    Copy(RefPtr<RotatedBuffer> aSource,
+         RefPtr<RotatedBuffer> aDestination,
+         gfx::IntRect aBounds)
+      : mSource(aSource)
+      , mDestination(aDestination)
+      , mBounds(aBounds)
+    {}
+
+    bool CopyBuffer();
+
+    RefPtr<RotatedBuffer> mSource;
+    RefPtr<RotatedBuffer> mDestination;
+    gfx::IntRect mBounds;
+  };
+
+  struct Unrotate {
+    Unrotate(RotatedBuffer::Parameters aParameters,
+             RefPtr<RotatedBuffer> aBuffer)
+      : mParameters(aParameters)
+      , mBuffer(aBuffer)
+    {}
+
+    bool UnrotateBuffer();
+
+    RotatedBuffer::Parameters mParameters;
+    RefPtr<RotatedBuffer> mBuffer;
+  };
+
+  /**
+   * Prepares the rotated buffers for painting by copying a previous frame
+   * into the buffer and/or unrotating the pixels and returns whether the
+   * operations were successful. If this fails a new buffer should be created
+   * for the frame.
+   */
+  bool PrepareBuffer();
+  void GetTextureClients(nsTArray<RefPtr<TextureClient>>& aTextureClients);
+
+  Maybe<Copy> mBufferCopy;
+  Maybe<Unrotate> mBufferUnrotate;
+
+protected:
+  ~CapturedBufferState() {}
+};
+
 typedef bool (*PrepDrawTargetForPaintingCallback)(CapturedPaintState* aPaintState);
 
 class CompositorBridgeChild;
 
 class PaintThread final
 {
   friend void DestroyPaintThread(UniquePtr<PaintThread>&& aPaintThread);
 
@@ -75,16 +126,18 @@ public:
   static bool IsOnPaintThread();
 
   // Must be called on the main thread. Signifies that a new layer transaction
   // is beginning. This must be called immediately after FlushAsyncPaints, and
   // before any new painting occurs, as there can't be any async paints queued
   // or running while this is executing.
   void BeginLayerTransaction();
 
+  void PrepareBuffer(CapturedBufferState* aState);
+
   void PaintContents(CapturedPaintState* aState,
                      PrepDrawTargetForPaintingCallback aCallback);
 
   // Must be called on the main thread. Signifies that the current
   // batch of CapturedPaintStates* for PaintContents have been recorded
   // and the main thread is finished recording this layer.
   void EndLayer();
 
@@ -105,16 +158,18 @@ public:
 
 private:
   PaintThread();
 
   bool Init();
   void ShutdownOnPaintThread();
   void InitOnPaintThread();
 
+  void AsyncPrepareBuffer(CompositorBridgeChild* aBridge,
+                          CapturedBufferState* aState);
   void AsyncPaintContents(CompositorBridgeChild* aBridge,
                           CapturedPaintState* aState,
                           PrepDrawTargetForPaintingCallback aCallback);
   void AsyncEndLayer();
   void AsyncEndLayerTransaction(CompositorBridgeChild* aBridge,
                                 SyncObjectClient* aSyncObject);
 
   static StaticAutoPtr<PaintThread> sSingleton;
--- a/gfx/layers/RotatedBuffer.cpp
+++ b/gfx/layers/RotatedBuffer.cpp
@@ -286,112 +286,124 @@ WrapRotationAxis(int32_t* aRotationPoint
   if (*aRotationPoint < 0) {
     *aRotationPoint += aSize;
   } else if (*aRotationPoint >= aSize) {
     *aRotationPoint -= aSize;
   }
 }
 
 bool
-RotatedBuffer::AdjustTo(const gfx::IntRect& aDestBufferRect,
-                        const gfx::IntRect& aDrawBounds,
-                        bool aCanHaveRotation,
-                        bool aCanDrawRotated)
+RotatedBuffer::Parameters::IsRotated() const
+{
+  return mBufferRotation != IntPoint(0,0);
+}
+
+bool
+RotatedBuffer::Parameters::RectWrapsBuffer(const gfx::IntRect& aRect) const
+{
+  int32_t xBoundary = mBufferRect.XMost() - mBufferRotation.x;
+  int32_t yBoundary = mBufferRect.YMost() - mBufferRotation.y;
+  return (aRect.x < xBoundary && xBoundary < aRect.XMost()) ||
+         (aRect.y < yBoundary && yBoundary < aRect.YMost());
+}
+
+void
+RotatedBuffer::Parameters::SetUnrotated()
+{
+  mBufferRotation = IntPoint(0,0);
+  mDidSelfCopy = true;
+}
+
+RotatedBuffer::Parameters
+RotatedBuffer::AdjustedParameters(const gfx::IntRect& aDestBufferRect) const
 {
   IntRect keepArea;
   if (keepArea.IntersectRect(aDestBufferRect, mBufferRect)) {
     // Set mBufferRotation so that the pixels currently in mDTBuffer
     // will still be rendered in the right place when mBufferRect
     // changes to aDestBufferRect.
     IntPoint newRotation = mBufferRotation +
       (aDestBufferRect.TopLeft() - mBufferRect.TopLeft());
     WrapRotationAxis(&newRotation.x, mBufferRect.Width());
     WrapRotationAxis(&newRotation.y, mBufferRect.Height());
     NS_ASSERTION(gfx::IntRect(gfx::IntPoint(0,0), mBufferRect.Size()).Contains(newRotation),
                  "newRotation out of bounds");
 
-    int32_t xBoundary = aDestBufferRect.XMost() - newRotation.x;
-    int32_t yBoundary = aDestBufferRect.YMost() - newRotation.y;
-    bool drawWrapsBuffer = (aDrawBounds.x < xBoundary && xBoundary < aDrawBounds.XMost()) ||
-                           (aDrawBounds.y < yBoundary && yBoundary < aDrawBounds.YMost());
+    return Parameters{aDestBufferRect, newRotation};
+  }
+
+  // No pixels are going to be kept. The whole visible region
+  // will be redrawn, so we don't need to copy anything, so we don't
+  // set destBuffer.
+  return Parameters{aDestBufferRect, IntPoint(0,0)};
+}
 
-    if ((drawWrapsBuffer && !aCanDrawRotated) ||
-        (newRotation != IntPoint(0,0) && !aCanHaveRotation)) {
-      // The stuff we need to redraw will wrap around an edge of the
-      // buffer (and the caller doesn't know how to support that), so
-      // move the pixels we can keep into a position that lets us
-      // redraw in just one quadrant.
-      RefPtr<gfx::DrawTarget> dtBuffer = GetDTBuffer();
-      RefPtr<gfx::DrawTarget> dtBufferOnWhite = GetDTBufferOnWhite();
+bool
+RotatedBuffer::UnrotateBufferTo(const Parameters& aParameters)
+{
+  RefPtr<gfx::DrawTarget> dtBuffer = GetDTBuffer();
+  RefPtr<gfx::DrawTarget> dtBufferOnWhite = GetDTBufferOnWhite();
 
-      if (mBufferRotation == IntPoint(0,0)) {
-        IntRect srcRect(IntPoint(0, 0), mBufferRect.Size());
-        IntPoint dest = mBufferRect.TopLeft() - aDestBufferRect.TopLeft();
-
-        MOZ_ASSERT(dtBuffer && dtBuffer->IsValid());
-        dtBuffer->CopyRect(srcRect, dest);
-        if (HaveBufferOnWhite()) {
-          MOZ_ASSERT(dtBufferOnWhite && dtBufferOnWhite->IsValid());
-          dtBufferOnWhite->CopyRect(srcRect, dest);
-        }
+  if (mBufferRotation == IntPoint(0,0)) {
+    IntRect srcRect(IntPoint(0, 0), mBufferRect.Size());
+    IntPoint dest = mBufferRect.TopLeft() - aParameters.mBufferRect.TopLeft();
 
-        mDidSelfCopy = true;
-        mBufferRect = aDestBufferRect;
-      } else {
-        // With azure and a data surface perform an buffer unrotate
-        // (SelfCopy).
-        unsigned char* data;
-        IntSize size;
-        int32_t stride;
-        SurfaceFormat format;
-
-        if (dtBuffer->LockBits(&data, &size, &stride, &format)) {
-          uint8_t bytesPerPixel = BytesPerPixel(format);
-          BufferUnrotate(data,
-                         size.width * bytesPerPixel,
-                         size.height, stride,
-                         newRotation.x * bytesPerPixel, newRotation.y);
-          dtBuffer->ReleaseBits(data);
+    MOZ_ASSERT(dtBuffer && dtBuffer->IsValid());
+    dtBuffer->CopyRect(srcRect, dest);
+    if (HaveBufferOnWhite()) {
+      MOZ_ASSERT(dtBufferOnWhite && dtBufferOnWhite->IsValid());
+      dtBufferOnWhite->CopyRect(srcRect, dest);
+    }
+  } else {
+    // With azure and a data surface perform an buffer unrotate
+    // (SelfCopy).
+    unsigned char* data;
+    IntSize size;
+    int32_t stride;
+    SurfaceFormat format;
 
-          if (HaveBufferOnWhite()) {
-            MOZ_ASSERT(dtBufferOnWhite && dtBufferOnWhite->IsValid());
-            dtBufferOnWhite->LockBits(&data, &size, &stride, &format);
-            uint8_t bytesPerPixel = BytesPerPixel(format);
-            BufferUnrotate(data,
-                           size.width * bytesPerPixel,
-                           size.height, stride,
-                           newRotation.x * bytesPerPixel, newRotation.y);
-            dtBufferOnWhite->ReleaseBits(data);
-          }
+    if (dtBuffer->LockBits(&data, &size, &stride, &format)) {
+      uint8_t bytesPerPixel = BytesPerPixel(format);
+      BufferUnrotate(data,
+                     size.width * bytesPerPixel,
+                     size.height, stride,
+                     aParameters.mBufferRotation.x * bytesPerPixel,
+                     aParameters.mBufferRotation.y);
+      dtBuffer->ReleaseBits(data);
 
-          // Buffer unrotate moves all the pixels
-          mDidSelfCopy = true;
-          mBufferRect = aDestBufferRect;
-          mBufferRotation = IntPoint(0, 0);
-        }
-
-        if (!mDidSelfCopy) {
-          // We couldn't unrotate the buffer, so we need to create a
-          // new one and start from scratch
-          return false;
-        }
+      if (HaveBufferOnWhite()) {
+        MOZ_ASSERT(dtBufferOnWhite && dtBufferOnWhite->IsValid());
+        dtBufferOnWhite->LockBits(&data, &size, &stride, &format);
+        uint8_t bytesPerPixel = BytesPerPixel(format);
+        BufferUnrotate(data,
+                       size.width * bytesPerPixel,
+                       size.height, stride,
+                       aParameters.mBufferRotation.x * bytesPerPixel,
+                       aParameters.mBufferRotation.y);
+        dtBufferOnWhite->ReleaseBits(data);
       }
     } else {
-      mBufferRect = aDestBufferRect;
-      mBufferRotation = newRotation;
+      return false;
     }
-  } else {
-    // No pixels are going to be kept. The whole visible region
-    // will be redrawn, so we don't need to copy anything, so we don't
-    // set destBuffer.
-    mBufferRect = aDestBufferRect;
-    mBufferRotation = IntPoint(0,0);
   }
+  return true;
+}
 
-  return true;
+void
+RotatedBuffer::SetParameters(const RotatedBuffer::Parameters& aParameters)
+{
+  mBufferRect = aParameters.mBufferRect;
+  mBufferRotation = aParameters.mBufferRotation;
+  mDidSelfCopy = aParameters.mDidSelfCopy;
+}
+
+RotatedBuffer::ContentType
+RotatedBuffer::GetContentType() const
+{
+  return ContentForFormat(GetFormat());
 }
 
 DrawTarget*
 RotatedBuffer::BorrowDrawTargetForQuadrantUpdate(const IntRect& aBounds,
                                                  ContextSource aSource,
                                                  DrawIterator* aIter,
                                                  bool aSetTransform,
                                                  Matrix* aOutMatrix)
@@ -480,27 +492,27 @@ RemoteRotatedBuffer::Lock(OpenMode aMode
   if (!locked) {
     Unlock();
     return false;
   }
 
   mTarget = mClient->BorrowDrawTarget();
   if (!mTarget || !mTarget->IsValid()) {
     gfxCriticalNote << "Invalid draw target " << hexa(mTarget)
-                    << "in RemoteRotatedBuffer::Lock";
+                    << " in RemoteRotatedBuffer::Lock";
     Unlock();
     return false;
   }
 
   if (mClientOnWhite) {
     mTargetOnWhite = mClientOnWhite->BorrowDrawTarget();
     if (!mTargetOnWhite || !mTargetOnWhite->IsValid()) {
       gfxCriticalNote << "Invalid draw target(s) " << hexa(mTarget)
                       << " and " << hexa(mTargetOnWhite)
-                      << "in RemoteRotatedBuffer::Lock";
+                      << " in RemoteRotatedBuffer::Lock";
       Unlock();
       return false;
     }
   }
 
   return true;
 }
 
--- a/gfx/layers/RotatedBuffer.h
+++ b/gfx/layers/RotatedBuffer.h
@@ -19,21 +19,19 @@
 #include "nsDebug.h"                    // for NS_RUNTIMEABORT
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsRegion.h"                   // for nsIntRegion
 #include "LayersTypes.h"
 
 namespace mozilla {
 namespace layers {
 
-class CapturedPaintState;
-
-typedef bool (*PrepDrawTargetForPaintingCallback)(CapturedPaintState*);
-
 class PaintedLayer;
+class CapturedBufferState;
+class ContentClient;
 
 // Mixin class for classes which need logic for loaning out a draw target.
 // See comments on BorrowDrawTargetForQuadrantUpdate.
 class BorrowDrawTarget
 {
 public:
   void ReturnDrawTarget(gfx::DrawTarget*& aReturned);
 
@@ -153,25 +151,48 @@ public:
    */
   gfx::DrawTarget*
   BorrowDrawTargetForQuadrantUpdate(const gfx::IntRect& aBounds,
                                     ContextSource aSource,
                                     DrawIterator* aIter,
                                     bool aSetTransform = true,
                                     gfx::Matrix* aOutTransform = nullptr);
 
+  struct Parameters {
+    Parameters(const gfx::IntRect& aBufferRect,
+               const gfx::IntPoint& aBufferRotation)
+      : mBufferRect(aBufferRect)
+      , mBufferRotation(aBufferRotation)
+      , mDidSelfCopy(false)
+    {
+    }
+
+    bool IsRotated() const;
+    bool RectWrapsBuffer(const gfx::IntRect& aRect) const;
+
+    void SetUnrotated();
+
+    gfx::IntRect  mBufferRect;
+    gfx::IntPoint mBufferRotation;
+    bool mDidSelfCopy;
+  };
+
   /**
-   * Adjusts the buffer to be centered on the destination buffer rect,
-   * and ready to draw the specified bounds. Returns whether a new buffer
-   * needs to be created.
+   * Returns the new buffer parameters for rotating to a
+   * destination buffer rect.
    */
-  bool AdjustTo(const gfx::IntRect& aDestBufferRect,
-                const gfx::IntRect& aDrawBounds,
-                bool aCanHaveRotation,
-                bool aCanDrawRotated);
+  Parameters AdjustedParameters(const gfx::IntRect& aDestBufferRect) const;
+
+  /**
+   * Unrotates the pixels of the rotated buffer for the specified
+   * new buffer parameters.
+   */
+  bool UnrotateBufferTo(const Parameters& aParameters);
+
+  void SetParameters(const Parameters& aParameters);
 
   /**
    * |BufferRect()| is the rect of device pixels that this
    * RotatedBuffer covers.  That is what DrawBufferWithRotation()
    * will paint when it's called.
    */
   const gfx::IntRect& BufferRect() const { return mBufferRect; }
   const gfx::IntPoint& BufferRotation() const { return mBufferRotation; }
@@ -199,30 +220,49 @@ public:
    */
   bool DidSelfCopy() const { return mDidSelfCopy; }
 
   /**
    * Clears the self copy flag.
    */
   void ClearDidSelfCopy() { mDidSelfCopy = false; }
 
+  /**
+   * Gets the content type for this buffer.
+   */
+  ContentType GetContentType() const;
+
   virtual bool IsLocked() = 0;
   virtual bool Lock(OpenMode aMode) = 0;
   virtual void Unlock() = 0;
 
   virtual bool HaveBuffer() const = 0;
   virtual bool HaveBufferOnWhite() const = 0;
 
   virtual gfx::SurfaceFormat GetFormat() const = 0;
 
   virtual already_AddRefed<gfx::SourceSurface> GetSourceSurface(ContextSource aSource) const = 0;
 
   virtual gfx::DrawTarget* GetDTBuffer() const = 0;
   virtual gfx::DrawTarget* GetDTBufferOnWhite() const = 0;
 
+  virtual TextureClient* GetClient() const {
+    return nullptr;
+  }
+  virtual TextureClient* GetClientOnWhite() const {
+    return nullptr;
+  }
+
+  /**
+   * Creates a shallow copy of the rotated buffer with the same underlying
+   * texture clients and draw targets. Rotated buffers are not thread safe,
+   * so a copy needs to be sent for off main thread painting.
+   */
+  virtual RefPtr<RotatedBuffer> ShallowCopy() const = 0;
+
 protected:
   virtual ~RotatedBuffer() {}
 
   enum XSide {
     LEFT, RIGHT
   };
   enum YSide {
     TOP, BOTTOM
@@ -290,23 +330,42 @@ public:
 
   virtual gfx::SurfaceFormat GetFormat() const override;
 
   virtual already_AddRefed<gfx::SourceSurface> GetSourceSurface(ContextSource aSource) const override;
 
   virtual gfx::DrawTarget* GetDTBuffer() const override;
   virtual gfx::DrawTarget* GetDTBufferOnWhite() const override;
 
-  TextureClient* GetClient() const { return mClient; }
-  TextureClient* GetClientOnWhite() const { return mClientOnWhite; }
+  virtual TextureClient* GetClient() const override { return mClient; }
+  virtual TextureClient* GetClientOnWhite() const override { return mClientOnWhite; }
+
+  virtual RefPtr<RotatedBuffer> ShallowCopy() const override {
+    return new RemoteRotatedBuffer {
+      mClient, mClientOnWhite,
+      mTarget, mTargetOnWhite,
+      mBufferRect, mBufferRotation
+    };
+  }
 
   void SyncWithObject(SyncObjectClient* aSyncObject);
   void Clear();
 
 private:
+  RemoteRotatedBuffer(TextureClient* aClient, TextureClient* aClientOnWhite,
+                      gfx::DrawTarget* aTarget, gfx::DrawTarget* aTargetOnWhite,
+                      const gfx::IntRect& aBufferRect,
+                      const gfx::IntPoint& aBufferRotation)
+    : RotatedBuffer(aBufferRect, aBufferRotation)
+    , mClient(aClient)
+    , mClientOnWhite(aClientOnWhite)
+    , mTarget(aTarget)
+    , mTargetOnWhite(aTargetOnWhite)
+  { }
+
   RefPtr<TextureClient> mClient;
   RefPtr<TextureClient> mClientOnWhite;
 
   RefPtr<gfx::DrawTarget> mTarget;
   RefPtr<gfx::DrawTarget> mTargetOnWhite;
 };
 
 /**
@@ -333,16 +392,23 @@ public:
 
   virtual gfx::SurfaceFormat GetFormat() const override;
 
   virtual already_AddRefed<gfx::SourceSurface> GetSourceSurface(ContextSource aSource) const override;
 
   virtual gfx::DrawTarget* GetDTBuffer() const override;
   virtual gfx::DrawTarget* GetDTBufferOnWhite() const override;
 
+  virtual RefPtr<RotatedBuffer> ShallowCopy() const override {
+    return new DrawTargetRotatedBuffer {
+        mTarget, mTargetOnWhite,
+        mBufferRect, mBufferRotation
+      };
+  }
+
 private:
   RefPtr<gfx::DrawTarget> mTarget;
   RefPtr<gfx::DrawTarget> mTargetOnWhite;
 };
 
 /**
  * SourceRotatedBuffer is a rotated buffer that is backed by source surfaces,
  * and may only be used to draw into other buffers or be read directly.
@@ -367,16 +433,20 @@ public:
   virtual gfx::SurfaceFormat GetFormat() const override;
 
   virtual bool HaveBuffer() const override { return !!mSource; }
   virtual bool HaveBufferOnWhite() const override { return !!mSourceOnWhite; }
 
   virtual gfx::DrawTarget* GetDTBuffer() const override { return nullptr; }
   virtual gfx::DrawTarget* GetDTBufferOnWhite() const override { return nullptr; }
 
+  virtual RefPtr<RotatedBuffer> ShallowCopy() const override {
+    return nullptr;
+  }
+
 private:
   RefPtr<gfx::SourceSurface> mSource;
   RefPtr<gfx::SourceSurface> mSourceOnWhite;
 };
 
 } // namespace layers
 } // namespace mozilla
 
--- a/gfx/layers/client/ClientPaintedLayer.cpp
+++ b/gfx/layers/client/ClientPaintedLayer.cpp
@@ -205,21 +205,27 @@ ClientPaintedLayer::PaintThebes(nsTArray
  *     the main thread. Sync OMTP is only meant to be used as a debugging tool.
  */
 bool
 ClientPaintedLayer::PaintOffMainThread()
 {
   uint32_t flags = GetPaintFlags();
 
   PaintState state = mContentClient->BeginPaint(this, flags | ContentClient::PAINT_ASYNC);
+  bool didUpdate = false;
+
+  if (state.mBufferState) {
+    PaintThread::Get()->PrepareBuffer(state.mBufferState);
+    didUpdate = true;
+  }
+
   if (!UpdatePaintRegion(state)) {
     return false;
   }
 
-  bool didUpdate = false;
   RotatedBuffer::DrawIterator iter;
 
   // Debug Protip: Change to BorrowDrawTargetForPainting if using sync OMTP.
   while (RefPtr<CapturedPaintState> captureState =
           mContentClient->BorrowDrawTargetForRecording(state, &iter))
   {
     DrawTarget* target = captureState->mTargetDual;
     if (!target || !target->IsValid()) {
--- a/gfx/layers/client/ContentClient.cpp
+++ b/gfx/layers/client/ContentClient.cpp
@@ -18,16 +18,17 @@
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
 #include "mozilla/gfx/BaseSize.h"       // for BaseSize
 #include "mozilla/gfx/Rect.h"           // for Rect
 #include "mozilla/gfx/Types.h"
 #include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/LayersMessages.h"  // for ThebesBufferData
 #include "mozilla/layers/LayersTypes.h"
+#include "mozilla/layers/PaintThread.h"
 #include "nsDebug.h"                    // for NS_ASSERTION, NS_WARNING, etc
 #include "nsISupportsImpl.h"            // for gfxContext::Release, etc
 #include "nsIWidget.h"                  // for nsIWidget
 #include "nsLayoutUtils.h"
 #ifdef XP_WIN
 #include "gfxWindowsPlatform.h"
 #endif
 #ifdef MOZ_WIDGET_GTK
@@ -122,123 +123,163 @@ ContentClient::BeginPaint(PaintedLayer* 
 
   if (!dest.mCanKeepBufferContents) {
     // We're effectively clearing the valid region, so we need to draw
     // the entire needed region now.
     MOZ_ASSERT(!dest.mCanReuseBuffer);
     MOZ_ASSERT(dest.mValidRegion.IsEmpty());
 
     result.mRegionToInvalidate = aLayer->GetValidRegion();
-    Clear();
 
 #if defined(MOZ_DUMP_PAINTING)
     if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
-      if (result.mContentType != BufferContentType()) {
+      if (result.mContentType != mBuffer->GetContentType()) {
         printf_stderr("Invalidating entire rotated buffer (layer %p): content type changed\n", aLayer);
       } else if ((dest.mBufferMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != mBuffer->HaveBufferOnWhite()) {
         printf_stderr("Invalidating entire rotated buffer (layer %p): component alpha changed\n", aLayer);
       }
     }
 #endif
+    Clear();
   }
 
   result.mRegionToDraw.Sub(dest.mNeededRegion,
                            dest.mValidRegion);
 
   if (result.mRegionToDraw.IsEmpty())
     return result;
 
-  OpenMode lockMode = aFlags & PAINT_ASYNC ? OpenMode::OPEN_READ_ASYNC_WRITE
-                                           : OpenMode::OPEN_READ_WRITE;
-
-  if (mBuffer) {
-    if (mBuffer->Lock(lockMode)) {
-      // Do not modify result.mRegionToDraw or result.mContentType after this call.
-      // Do not modify the back buffer's bufferRect, bufferRotation, or didSelfCopy.
-      FinalizeFrame(result.mRegionToDraw);
-    } else {
-      // Abandon everything and redraw it all. Ideally we'd reallocate and copy
-      // the old to the new and then call FinalizeFrame on the new buffer so that
-      // we only need to draw the latest bits, but we need a big refactor to support
-      // that ordering.
-      result.mRegionToDraw = dest.mNeededRegion;
-      dest.mCanReuseBuffer = false;
-      Clear();
-    }
-  }
-
   // We need to disable rotation if we're going to be resampled when
   // drawing, because we might sample across the rotation boundary.
   // Also disable buffer rotation when using webrender.
   bool canHaveRotation = gfxPlatform::BufferRotationEnabled() &&
                          !(aFlags & (PAINT_WILL_RESAMPLE | PAINT_NO_ROTATION)) &&
                          !(aLayer->Manager()->AsWebRenderLayerManager());
   bool canDrawRotated = aFlags & PAINT_CAN_DRAW_ROTATED;
+  bool asyncPaint = (aFlags & PAINT_ASYNC);
 
   IntRect drawBounds = result.mRegionToDraw.GetBounds();
-  RefPtr<RotatedBuffer> newBuffer;
-  uint32_t bufferFlags = 0;
-  if (dest.mBufferMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
-    bufferFlags |= BUFFER_COMPONENT_ALPHA;
-  }
-  if (dest.mCanReuseBuffer && mBuffer) {
-    if (!mBuffer->AdjustTo(dest.mBufferRect,
-                           drawBounds,
-                           canHaveRotation,
-                           canDrawRotated)) {
-      dest.mBufferRect = ComputeBufferRect(dest.mNeededRegion.GetBounds());
-      newBuffer = CreateBuffer(result.mContentType, dest.mBufferRect, bufferFlags);
+  OpenMode lockMode = asyncPaint ? OpenMode::OPEN_READ_ASYNC_WRITE
+                                 : OpenMode::OPEN_READ_WRITE;
+
+  if (dest.mCanReuseBuffer) {
+    MOZ_ASSERT(mBuffer);
+
+    bool canReuseBuffer = false;
+
+    if (mBuffer->Lock(lockMode)) {
+      RefPtr<CapturedBufferState> bufferState = new CapturedBufferState();
+
+      // Do not modify result.mRegionToDraw or result.mContentType after this call.
+      FinalizeFrame(result.mRegionToDraw, bufferState);
+
+      auto newParameters = mBuffer->AdjustedParameters(dest.mBufferRect);
+
+      if ((!canHaveRotation && newParameters.IsRotated()) ||
+          (!canDrawRotated && newParameters.RectWrapsBuffer(drawBounds))) {
+        bufferState->mBufferUnrotate = Some(CapturedBufferState::Unrotate {
+          newParameters,
+          mBuffer->ShallowCopy(),
+        });
+      }
 
-      if (!newBuffer) {
-        if (Factory::ReasonableSurfaceSize(IntSize(dest.mBufferRect.Width(), dest.mBufferRect.Height()))) {
-          gfxCriticalNote << "Failed 1 buffer for "
-                          << dest.mBufferRect.x << ", "
-                          << dest.mBufferRect.y << ", "
-                          << dest.mBufferRect.Width() << ", "
-                          << dest.mBufferRect.Height();
+      // If we're async painting then return the buffer state to
+      // be dispatched to the paint thread, otherwise do it now
+      if (asyncPaint) {
+        // We cannot do a buffer unrotate if the buffer is already rotated
+        // and we're async painting as that may fail
+        if (!bufferState->mBufferUnrotate ||
+            mBuffer->BufferRotation() == IntPoint(0,0)) {
+          result.mBufferState = bufferState;
+
+          // We can then assume that preparing the buffer will always
+          // succeed and update our parameters unconditionally
+          if (bufferState->mBufferUnrotate) {
+            newParameters.SetUnrotated();
+          }
+          mBuffer->SetParameters(newParameters);
+          canReuseBuffer = true;
         }
-        return result;
+      } else {
+        if (bufferState->PrepareBuffer()) {
+          if (bufferState->mBufferUnrotate) {
+            newParameters.SetUnrotated();
+          }
+          mBuffer->SetParameters(newParameters);
+          canReuseBuffer = true;
+        }
       }
     }
-  } else {
-    // The buffer's not big enough, so allocate a new one
-    newBuffer = CreateBuffer(result.mContentType, dest.mBufferRect, bufferFlags);
+
+    if (!canReuseBuffer) {
+      if (mBuffer->IsLocked()) {
+        mBuffer->Unlock();
+      }
+      dest.mBufferRect = ComputeBufferRect(dest.mNeededRegion.GetBounds());
+      dest.mCanReuseBuffer = false;
+    }
+  }
+
+  NS_ASSERTION(!(aFlags & PAINT_WILL_RESAMPLE) || dest.mBufferRect == dest.mNeededRegion.GetBounds(),
+               "If we're resampling, we need to validate the entire buffer");
+
+  // We never had a buffer, the buffer wasn't big enough, the content changed
+  // types, or we failed to unrotate the buffer when requested. In any case,
+  // we need to allocate a new one and prepare it for drawing.
+  if (!dest.mCanReuseBuffer) {
+    uint32_t bufferFlags = 0;
+    if (dest.mBufferMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
+      bufferFlags |= BUFFER_COMPONENT_ALPHA;
+    }
+
+    RefPtr<RotatedBuffer> newBuffer = CreateBuffer(result.mContentType,
+                                                   dest.mBufferRect,
+                                                   bufferFlags);
+
     if (!newBuffer) {
       if (Factory::ReasonableSurfaceSize(IntSize(dest.mBufferRect.Width(), dest.mBufferRect.Height()))) {
-        gfxCriticalNote << "Failed 2 buffer for "
+        gfxCriticalNote << "Failed buffer for "
                         << dest.mBufferRect.x << ", "
                         << dest.mBufferRect.y << ", "
                         << dest.mBufferRect.Width() << ", "
                         << dest.mBufferRect.Height();
       }
       return result;
     }
-  }
 
-  NS_ASSERTION(!(aFlags & PAINT_WILL_RESAMPLE) || dest.mBufferRect == dest.mNeededRegion.GetBounds(),
-               "If we're resampling, we need to validate the entire buffer");
-
-  // If needed, copy the old buffer over to the new one
-  if (newBuffer) {
     if (!newBuffer->Lock(lockMode)) {
       gfxCriticalNote << "Failed to lock new back buffer.";
-      Clear();
       return result;
     }
 
-    if (mBuffer) {
-      newBuffer->UpdateDestinationFrom(*mBuffer, newBuffer->BufferRect());
+    // If we have an existing front buffer, copy it into the new back buffer
+    if (RefPtr<RotatedBuffer> frontBuffer = GetFrontBuffer()) {
+      RefPtr<CapturedBufferState> bufferState = new CapturedBufferState();
+
+      bufferState->mBufferCopy = Some(CapturedBufferState::Copy {
+        frontBuffer->ShallowCopy(),
+        newBuffer->ShallowCopy(),
+        newBuffer->BufferRect(),
+      });
 
-      // We are done with the old back buffer now and it is about to be
-      // destroyed, so unlock it.
-      mBuffer->Unlock();
+      // If we're async painting then return the buffer state to
+      // be dispatched to the paint thread, otherwise do it now
+      if (asyncPaint) {
+        MOZ_ASSERT(!result.mBufferState);
+        result.mBufferState = bufferState;
+      } else {
+        if (!bufferState->PrepareBuffer()) {
+          gfxCriticalNote << "Failed to copy front buffer to back buffer.";
+          return result;
+        }
+      }
     }
 
     // Ensure our reference to the front buffer is released
-    // as well as the old back buffer.
+    // as well as the old back buffer
     Clear();
 
     mBuffer = newBuffer;
   }
 
   NS_ASSERTION(canHaveRotation || mBuffer->BufferRotation() == IntPoint(0,0),
                "Rotation disabled, but we have nonzero rotation?");
 
@@ -379,17 +420,19 @@ ContentClient::CalculateBufferForPaint(P
   nsIntRegion validRegion = aLayer->GetValidRegion();
 
   bool canReuseBuffer = !!mBuffer;
   bool canKeepBufferContents = true;
 
   while (true) {
     mode = aLayer->GetSurfaceMode();
     neededRegion = aLayer->GetVisibleRegion().ToUnknownRegion();
-    canReuseBuffer = canReuseBuffer && BufferSizeOkFor(neededRegion.GetBounds().Size());
+    canReuseBuffer = canReuseBuffer && ValidBufferSize(mBufferSizePolicy,
+                                                       mBuffer->BufferRect().Size(),
+                                                       neededRegion.GetBounds().Size());
     contentType = layerContentType;
 
     if (canReuseBuffer) {
       if (mBuffer->BufferRect().Contains(neededRegion.GetBounds())) {
         // We don't need to adjust mBufferRect.
         destBufferRect = mBuffer->BufferRect();
       } else if (neededRegion.GetBounds().Size() <= mBuffer->BufferRect().Size()) {
         // The buffer's big enough but doesn't contain everything that's
@@ -430,21 +473,22 @@ ContentClient::CalculateBufferForPaint(P
 
       // We need to validate the entire buffer, to make sure that only valid
       // pixels are sampled.
       neededRegion = destBufferRect;
     }
 
     // If we have an existing buffer, but the content type has changed or we
     // have transitioned into/out of component alpha, then we need to recreate it.
-    if (canKeepBufferContents &&
-        mBuffer &&
-        (contentType != BufferContentType() ||
-        (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != mBuffer->HaveBufferOnWhite()))
-    {
+    RefPtr<RotatedBuffer> frontBuffer = GetFrontBuffer();
+    bool needsComponentAlpha = (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA);
+    bool changedSurfaceOrContent = frontBuffer &&
+                                   (contentType != frontBuffer->GetContentType() ||
+                                    needsComponentAlpha != frontBuffer->HaveBufferOnWhite());
+    if (canKeepBufferContents && changedSurfaceOrContent) {
       // Restart the decision process; we won't re-enter since we guard on
       // being able to keep the buffer contents.
       canReuseBuffer = false;
       canKeepBufferContents = false;
       validRegion.SetEmpty();
       continue;
     }
 
@@ -460,32 +504,30 @@ ContentClient::CalculateBufferForPaint(P
   dest.mBufferRect = destBufferRect;
   dest.mBufferMode = mode;
   dest.mBufferContentType = contentType;
   dest.mCanReuseBuffer = canReuseBuffer;
   dest.mCanKeepBufferContents = canKeepBufferContents;
   return dest;
 }
 
-gfxContentType
-ContentClient::BufferContentType()
+bool
+ContentClient::ValidBufferSize(BufferSizePolicy aPolicy,
+                               const gfx::IntSize& aBufferSize,
+                               const gfx::IntSize& aVisibleBoundsSize)
 {
-  if (mBuffer) {
-    return ContentForFormat(mBuffer->GetFormat());
-  }
-  return gfxContentType::SENTINEL;
+  return (aVisibleBoundsSize == aBufferSize ||
+          (SizedToVisibleBounds != aPolicy &&
+           aVisibleBoundsSize < aBufferSize));
 }
 
-bool
-ContentClient::BufferSizeOkFor(const IntSize& aSize)
+RefPtr<RotatedBuffer>
+ContentClient::GetFrontBuffer() const
 {
-  MOZ_ASSERT(mBuffer);
-  return (aSize == mBuffer->BufferRect().Size() ||
-          (SizedToVisibleBounds != mBufferSizePolicy &&
-           aSize < mBuffer->BufferRect().Size()));
+  return mBuffer;
 }
 
 void
 ContentClient::PrintInfo(std::stringstream& aStream, const char* aPrefix)
 {
   aStream << aPrefix;
   aStream << nsPrintfCString("ContentClient (0x%p)", this).get();
 }
@@ -836,97 +878,85 @@ ContentClientDoubleBuffered::SwapBuffers
 
   mFrontAndBackBufferDiffer = true;
 }
 
 ContentClient::PaintState
 ContentClientDoubleBuffered::BeginPaint(PaintedLayer* aLayer,
                                         uint32_t aFlags)
 {
-  EnsureBackBufferIfFrontBuffer();
-
   mIsNewBuffer = false;
-
   if (!mFrontBuffer || !mBuffer) {
     mFrontAndBackBufferDiffer = false;
   }
 
-  if (mFrontAndBackBufferDiffer) {
-    if (mFrontBuffer->DidSelfCopy()) {
-      // We can't easily draw our front buffer into us, since we're going to be
-      // copying stuff around anyway it's easiest if we just move our situation
-      // to non-rotated while we're at it. If this situation occurs we'll have
-      // hit a self-copy path in PaintThebes before as well anyway.
-      gfx::IntRect backBufferRect = mBuffer->BufferRect();
-      backBufferRect.MoveTo(mFrontBuffer->BufferRect().TopLeft());
+  return ContentClient::BeginPaint(aLayer, aFlags);
+}
 
-      mBuffer->SetBufferRect(backBufferRect);
-      mBuffer->SetBufferRotation(IntPoint(0,0));
-    } else {
-      mBuffer->SetBufferRect(mFrontBuffer->BufferRect());
-      mBuffer->SetBufferRotation(mFrontBuffer->BufferRotation());
-    }
-  }
-
-  return ContentClient::BeginPaint(aLayer, aFlags);
+RefPtr<RotatedBuffer>
+ContentClientDoubleBuffered::GetFrontBuffer() const
+{
+  return mFrontBuffer;
 }
 
 // Sync front/back buffers content
 // After executing, the new back buffer has the same (interesting) pixels as
 // the new front buffer, and mValidRegion et al. are correct wrt the new
 // back buffer (i.e. as they were for the old back buffer)
 void
-ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw)
+ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw,
+                                           CapturedBufferState* aPrepareState)
 {
   if (!mFrontAndBackBufferDiffer) {
-    MOZ_ASSERT(!mFrontBuffer->DidSelfCopy(), "If we have to copy the world, then our buffers are different, right?");
+    MOZ_ASSERT(!mFrontBuffer || !mFrontBuffer->DidSelfCopy(),
+               "If the front buffer did a self copy then our front and back buffer must be different.");
     return;
   }
-  MOZ_ASSERT(mFrontBuffer);
-  if (!mFrontBuffer) {
+
+  MOZ_ASSERT(mFrontBuffer && mBuffer);
+  if (!mFrontBuffer || !mBuffer) {
     return;
   }
 
   MOZ_LAYERS_LOG(("BasicShadowableThebes(%p): reading back <x=%d,y=%d,w=%d,h=%d>",
                   this,
                   mFrontUpdatedRegion.GetBounds().x,
                   mFrontUpdatedRegion.GetBounds().y,
                   mFrontUpdatedRegion.GetBounds().Width(),
                   mFrontUpdatedRegion.GetBounds().Height()));
 
   mFrontAndBackBufferDiffer = false;
 
+  // Move the back buffer rect and rotation to the front buffer rect and rotation
+  // so that we can update the pixels that changed between frames
+  gfx::IntRect backBufferRect = mBuffer->BufferRect();
+  backBufferRect.MoveTo(mFrontBuffer->BufferRect().TopLeft());
+  mBuffer->SetBufferRect(backBufferRect);
+  mBuffer->SetBufferRotation(mBuffer->BufferRotation());
+
+  // Calculate the region to update
   nsIntRegion updateRegion = mFrontUpdatedRegion;
   if (mFrontBuffer->DidSelfCopy()) {
+    // If we did an unrotate operation on the front buffer we might as well
+    // unrotate as well because we will be reading back the whole front buffer
+    mBuffer->SetBufferRotation(IntPoint(0,0));
+
     mFrontBuffer->ClearDidSelfCopy();
     updateRegion = mBuffer->BufferRect();
   }
 
   // No point in sync'ing what we are going to draw over anyway. And if there is
   // nothing to sync at all, there is nothing to do and we can go home early.
   updateRegion.Sub(updateRegion, aRegionToDraw);
   if (updateRegion.IsEmpty()) {
     return;
   }
 
-  if (!mBuffer) {
-    return;
-  }
-
-  if (mFrontBuffer->Lock(OpenMode::OPEN_READ_ONLY)) {
-    mBuffer->UpdateDestinationFrom(*mFrontBuffer, updateRegion.GetBounds());
-    mFrontBuffer->Unlock();
-  }
-}
-
-void
-ContentClientDoubleBuffered::EnsureBackBufferIfFrontBuffer()
-{
-  if (!mBuffer && mFrontBuffer) {
-    mBuffer = CreateBufferInternal(mFrontBuffer->BufferRect(),
-                                   mFrontBuffer->GetFormat(),
-                                   mTextureFlags);
-    MOZ_ASSERT(mBuffer);
-  }
+  MOZ_ASSERT(!aPrepareState->mBufferCopy);
+  aPrepareState->mBufferCopy = Some(CapturedBufferState::Copy {
+    mFrontBuffer->ShallowCopy(),
+    mBuffer->ShallowCopy(),
+    updateRegion.GetBounds(),
+  });
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/client/ContentClient.h
+++ b/gfx/layers/client/ContentClient.h
@@ -17,16 +17,17 @@
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/layers/CompositableClient.h"  // for CompositableClient
 #include "mozilla/layers/CompositableForwarder.h"
 #include "mozilla/layers/CompositorTypes.h"  // for TextureInfo, etc
 #include "mozilla/layers/ISurfaceAllocator.h"
 #include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor
 #include "mozilla/layers/LayersTypes.h"  // for TextureDumpMode
 #include "mozilla/layers/TextureClient.h"  // for TextureClient
+#include "mozilla/layers/PaintThread.h"  // for CapturedBufferState
 #include "mozilla/Maybe.h"              // for Maybe
 #include "mozilla/mozalloc.h"           // for operator delete
 #include "ReadbackProcessor.h"          // For ReadbackProcessor::Update
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsRect.h"                     // for mozilla::gfx::IntRect
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsTArray.h"                   // for nsTArray
@@ -35,16 +36,19 @@ namespace mozilla {
 namespace gfx {
 class DrawTarget;
 } // namespace gfx
 
 namespace layers {
 
 class PaintedLayer;
 class CapturedPaintState;
+class CapturedBufferState;
+
+typedef bool (*PrepDrawTargetForPaintingCallback)(CapturedPaintState*);
 
 /**
  * A compositable client for PaintedLayers. These are different to Image/Canvas
  * clients due to sending a valid region across IPC and because we do a lot more
  * optimisation work, encapsulated in RotatedBuffers.
  *
  * We use content clients for OMTC and non-OMTC, basic rendering so that
  * BasicPaintedLayer has only one interface to deal with. We support single and
@@ -109,16 +113,17 @@ public:
       , mContentType(gfxContentType::SENTINEL)
     {}
 
     nsIntRegion mRegionToDraw;
     nsIntRegion mRegionToInvalidate;
     SurfaceMode mMode;
     DrawRegionClip mClip;
     gfxContentType mContentType;
+    RefPtr<CapturedBufferState> mBufferState;
   };
 
   enum {
     PAINT_WILL_RESAMPLE = 0x01,
     PAINT_NO_ROTATION = 0x02,
     PAINT_CAN_DRAW_ROTATED = 0x04,
     PAINT_ASYNC = 0x08,
   };
@@ -198,34 +203,31 @@ protected:
   /**
    * Decide whether we can keep our current buffer and its contents,
    * and return a struct containing the regions to paint, invalidate,
    * the new buffer rect, surface mode, and content type.
    */
   BufferDecision CalculateBufferForPaint(PaintedLayer* aLayer,
                                          uint32_t aFlags);
 
-  /**
-   * Return the buffer's content type.  Requires a valid buffer.
-   */
-  gfxContentType BufferContentType();
-  /**
-   * Returns whether the specified size is adequate for the current
-   * buffer and buffer size policy.
-   */
-  bool BufferSizeOkFor(const gfx::IntSize& aSize);
+  static bool ValidBufferSize(BufferSizePolicy aPolicy,
+                              const gfx::IntSize& aBufferSize,
+                              const gfx::IntSize& aVisibleBoundsSize);
+
+  virtual RefPtr<RotatedBuffer> GetFrontBuffer() const;
 
   /**
    * Any actions that should be performed at the last moment before we begin
    * rendering the next frame. I.e., after we calculate what we will draw,
    * but before we rotate the buffer and possibly create new buffers.
    * aRegionToDraw is the region which is guaranteed to be overwritten when
    * drawing the next frame.
    */
-  virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw) {}
+  virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw,
+                             CapturedBufferState* aState) {}
 
   /**
    * Create a new rotated buffer for the specified content type, buffer rect,
    * and buffer flags.
    */
   virtual RefPtr<RotatedBuffer> CreateBuffer(gfxContentType aType,
                                              const gfx::IntRect& aRect,
                                              uint32_t aFlags) = 0;
@@ -358,26 +360,27 @@ public:
                     TextureDumpMode aCompress=TextureDumpMode::Compress) override;
 
   virtual void Clear() override;
 
   virtual void SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) override;
 
   virtual PaintState BeginPaint(PaintedLayer* aLayer, uint32_t aFlags) override;
 
-  virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw) override;
+  virtual RefPtr<RotatedBuffer> GetFrontBuffer() const override;
+
+  virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw,
+                             CapturedBufferState* aState) override;
 
   virtual TextureInfo GetTextureInfo() const override
   {
     return TextureInfo(CompositableType::CONTENT_DOUBLE, mTextureFlags);
   }
 
 private:
-  void EnsureBackBufferIfFrontBuffer();
-
   RefPtr<RemoteRotatedBuffer> mFrontBuffer;
   nsIntRegion mFrontUpdatedRegion;
   bool mFrontAndBackBufferDiffer;
 };
 
 /**
  * A single buffered ContentClientRemoteBuffer. We have a single
  * TextureClient/Host which we update and then send a message to the
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -687,20 +687,16 @@ TextureClient::BorrowDrawTarget()
   // the DrawTarget, just to get a snapshot, which is legit in term of OpenMode
   // but we should have a way to get a SourceSurface directly instead.
   //MOZ_ASSERT(mOpenMode & OpenMode::OPEN_WRITE);
 
   if (!IsValid() || !mIsLocked) {
     return nullptr;
   }
 
-  if (!NS_IsMainThread()) {
-    return nullptr;
-  }
-
   if (!mBorrowedDrawTarget) {
     mBorrowedDrawTarget = mData->BorrowDrawTarget();
 #ifdef DEBUG
     mExpectedDtRefs = mBorrowedDrawTarget ? mBorrowedDrawTarget->refCount() : 0;
 #endif
   }
 
   return mBorrowedDrawTarget;
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -759,17 +759,17 @@ CreateTextureHostD3D11(const SurfaceDesc
   }
   return result.forget();
 }
 
 
 already_AddRefed<DrawTarget>
 D3D11TextureData::BorrowDrawTarget()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(NS_IsMainThread() || PaintThread::IsOnPaintThread());
 
   if (!mDrawTarget && mTexture) {
     // This may return a null DrawTarget
     mDrawTarget = Factory::CreateDrawTargetForD3D11Texture(mTexture, mFormat);
     if (!mDrawTarget) {
       gfxCriticalNote << "Could not borrow DrawTarget (D3D11) " << (int)mFormat;
     }
   }
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -1189,16 +1189,44 @@ CompositorBridgeChild::FlushAsyncPaints(
 
     double ratio = double(mSlowFlushCount) / double(mTotalFlushCount);
     Telemetry::ScalarSet(Telemetry::ScalarID::GFX_OMTP_PAINT_WAIT_RATIO,
                          uint32_t(ratio * 100 * 100));
   }
 }
 
 void
+CompositorBridgeChild::NotifyBeginAsyncPrepareBuffer(CapturedBufferState* aState)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  MonitorAutoLock lock(mPaintLock);
+
+  // We must not be waiting for paints (or buffer copying) to complete yet. This
+  // would imply we started a new paint without waiting for a previous one, which
+  // could lead to incorrect rendering or IPDL deadlocks.
+  MOZ_ASSERT(!mIsDelayingForAsyncPaints);
+
+  mOutstandingAsyncPaints++;
+
+  // Mark texture clients that they are being used for async painting, and
+  // make sure we hold them alive on the main thread.
+  aState->GetTextureClients(mTextureClientsForAsyncPaint);
+}
+
+void
+CompositorBridgeChild::NotifyFinishedAsyncPrepareBuffer(CapturedBufferState* aState)
+{
+  MOZ_ASSERT(PaintThread::IsOnPaintThread());
+
+  MonitorAutoLock lock(mPaintLock);
+  mOutstandingAsyncPaints--;
+}
+
+void
 CompositorBridgeChild::NotifyBeginAsyncPaint(CapturedPaintState* aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   MonitorAutoLock lock(mPaintLock);
 
   // We must not be waiting for paints to complete yet. This would imply we
   // started a new paint without waiting for a previous one, which could lead to
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -40,16 +40,17 @@ using mozilla::dom::TabChild;
 class IAPZCTreeManager;
 class APZCTreeManagerChild;
 class ClientLayerManager;
 class CompositorBridgeParent;
 class CompositorManagerChild;
 class CompositorOptions;
 class TextureClient;
 class TextureClientPool;
+class CapturedBufferState;
 class CapturedPaintState;
 struct FrameMetrics;
 
 class CompositorBridgeChild final : public PCompositorBridgeChild,
                                     public TextureForwarder
 {
   typedef InfallibleTArray<AsyncParentMessageData> AsyncParentMessageArray;
 
@@ -223,16 +224,24 @@ public:
   wr::PipelineId GetNextPipelineId();
 
   // Must only be called from the main thread. Ensures that any paints from
   // previous frames have been flushed. The main thread blocks until the
   // operation completes.
   void FlushAsyncPaints();
 
   // Must only be called from the main thread. Notifies the CompositorBridge
+  // that the paint thread is going to begin preparing a buffer asynchronously.
+  void NotifyBeginAsyncPrepareBuffer(CapturedBufferState* aState);
+
+  // Must only be called from the paint thread. Notifies the CompositorBridge
+  // that the paint thread has finished an asynchronous buffer prepare.
+  void NotifyFinishedAsyncPrepareBuffer(CapturedBufferState* aState);
+
+  // Must only be called from the main thread. Notifies the CompositorBridge
   // that the paint thread is going to begin painting asynchronously.
   void NotifyBeginAsyncPaint(CapturedPaintState* aState);
 
   // Must only be called from the paint thread. Notifies the CompositorBridge
   // that the paint thread has finished an asynchronous paint request.
   void NotifyFinishedAsyncPaint(CapturedPaintState* aState);
 
   // Must only be called from the main thread. Notifies the CompositorBridge
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -218,16 +218,17 @@ EXPORTS.mozilla.layers += [
     'opengl/CompositorOGL.h',
     'opengl/MacIOSurfaceTextureClientOGL.h',
     'opengl/MacIOSurfaceTextureHostOGL.h',
     'opengl/TextureClientOGL.h',
     'opengl/TextureHostOGL.h',
     'PaintThread.h',
     'PersistentBufferProvider.h',
     'RenderTrace.h',
+    'RotatedBuffer.h',
     'ShareableCanvasRenderer.h',
     'SourceSurfaceSharedData.h',
     'SourceSurfaceVolatileData.h',
     'SyncObject.h',
     'TextureSourceProvider.h',
     'TextureWrapperImage.h',
     'TransactionIdAllocator.h',
     'UpdateImageHelper.h',
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -1136,20 +1136,18 @@ WebRenderBridgeParent::SampleAnimations(
 void
 WebRenderBridgeParent::CompositeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect)
 {
   AUTO_PROFILER_TRACING("Paint", "CompositeToTraget");
   if (mPaused) {
     return;
   }
 
-  const uint32_t maxPendingFrameCount = 1;
-
   if (!mForceRendering &&
-      wr::RenderThread::Get()->GetPendingFrameCount(mApi->GetId()) >= maxPendingFrameCount) {
+      wr::RenderThread::Get()->TooManyPendingFrames(mApi->GetId())) {
     // Render thread is busy, try next time.
     ScheduleComposition();
     return;
   }
 
   bool scheduleComposite = false;
   nsTArray<wr::WrOpacityProperty> opacityArray;
   nsTArray<wr::WrTransformProperty> transformArray;
--- a/gfx/webrender_bindings/RenderThread.cpp
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -18,17 +18,17 @@
 
 namespace mozilla {
 namespace wr {
 
 static StaticRefPtr<RenderThread> sRenderThread;
 
 RenderThread::RenderThread(base::Thread* aThread)
   : mThread(aThread)
-  , mPendingFrameCountMapLock("RenderThread.mPendingFrameCountMapLock")
+  , mFrameCountMapLock("RenderThread.mFrameCountMapLock")
   , mRenderTextureMapLock("RenderThread.mRenderTextureMapLock")
   , mHasShutdown(false)
 {
 
 }
 
 RenderThread::~RenderThread()
 {
@@ -112,32 +112,32 @@ RenderThread::AddRenderer(wr::WindowId a
   MOZ_ASSERT(IsInRenderThread());
 
   if (mHasShutdown) {
     return;
   }
 
   mRenderers[aWindowId] = Move(aRenderer);
 
-  MutexAutoLock lock(mPendingFrameCountMapLock);
-  mPendingFrameCounts.Put(AsUint64(aWindowId), 0);
+  MutexAutoLock lock(mFrameCountMapLock);
+  mPendingFrameCounts.Put(AsUint64(aWindowId), FrameCount());
 }
 
 void
 RenderThread::RemoveRenderer(wr::WindowId aWindowId)
 {
   MOZ_ASSERT(IsInRenderThread());
 
   if (mHasShutdown) {
     return;
   }
 
   mRenderers.erase(aWindowId);
 
-  MutexAutoLock lock(mPendingFrameCountMapLock);
+  MutexAutoLock lock(mFrameCountMapLock);
   mPendingFrameCounts.Remove(AsUint64(aWindowId));
 }
 
 RendererOGL*
 RenderThread::GetRenderer(wr::WindowId aWindowId)
 {
   MOZ_ASSERT(IsInRenderThread());
 
@@ -260,56 +260,87 @@ RenderThread::Resume(wr::WindowId aWindo
   MOZ_ASSERT(it != mRenderers.end());
   if (it == mRenderers.end()) {
     return false;
   }
   auto& renderer = it->second;
   return renderer->Resume();
 }
 
-uint32_t
-RenderThread::GetPendingFrameCount(wr::WindowId aWindowId)
+bool
+RenderThread::TooManyPendingFrames(wr::WindowId aWindowId)
 {
-  MutexAutoLock lock(mPendingFrameCountMapLock);
-  uint32_t count = 0;
-  MOZ_ASSERT(mPendingFrameCounts.Get(AsUint64(aWindowId), &count));
-  mPendingFrameCounts.Get(AsUint64(aWindowId), &count);
-  return count;
+  const int64_t maxFrameCount = 1;
+
+  // Too many pending frames if pending frames exit more than maxFrameCount
+  // or if RenderBackend is still processing a frame.
+
+  MutexAutoLock lock(mFrameCountMapLock);
+  FrameCount count;
+  if (!mPendingFrameCounts.Get(AsUint64(aWindowId), &count)) {
+    MOZ_ASSERT(false);
+    return true;
+  }
+
+  if (count.mPendingCount > maxFrameCount) {
+    return true;
+  }
+  MOZ_ASSERT(count.mPendingCount >= count.mRenderingCount);
+  return count.mPendingCount > count.mRenderingCount;
 }
 
 void
 RenderThread::IncPendingFrameCount(wr::WindowId aWindowId)
 {
-  MutexAutoLock lock(mPendingFrameCountMapLock);
+  MutexAutoLock lock(mFrameCountMapLock);
   // Get the old count.
-  uint32_t oldCount = 0;
-  if (!mPendingFrameCounts.Get(AsUint64(aWindowId), &oldCount)) {
+  FrameCount count;
+  if (!mPendingFrameCounts.Get(AsUint64(aWindowId), &count)) {
     MOZ_ASSERT(false);
     return;
   }
   // Update pending frame count.
-  mPendingFrameCounts.Put(AsUint64(aWindowId), oldCount + 1);
+  count.mPendingCount = count.mPendingCount + 1;
+  mPendingFrameCounts.Put(AsUint64(aWindowId), count);
+}
+
+void
+RenderThread::IncRenderingFrameCount(wr::WindowId aWindowId)
+{
+  MutexAutoLock lock(mFrameCountMapLock);
+  // Get the old count.
+  FrameCount count;
+  if (!mPendingFrameCounts.Get(AsUint64(aWindowId), &count)) {
+    MOZ_ASSERT(false);
+    return;
+  }
+  // Update rendering frame count.
+  count.mRenderingCount = count.mRenderingCount + 1;
+  mPendingFrameCounts.Put(AsUint64(aWindowId), count);
 }
 
 void
 RenderThread::DecPendingFrameCount(wr::WindowId aWindowId)
 {
-  MutexAutoLock lock(mPendingFrameCountMapLock);
+  MutexAutoLock lock(mFrameCountMapLock);
   // Get the old count.
-  uint32_t oldCount = 0;
-  if (!mPendingFrameCounts.Get(AsUint64(aWindowId), &oldCount)) {
+  FrameCount count;
+  if (!mPendingFrameCounts.Get(AsUint64(aWindowId), &count)) {
     MOZ_ASSERT(false);
     return;
   }
-  MOZ_ASSERT(oldCount > 0);
-  if (oldCount <= 0) {
+  MOZ_ASSERT(count.mPendingCount > 0);
+  MOZ_ASSERT(count.mRenderingCount > 0);
+  if (count.mPendingCount <= 0) {
     return;
   }
-  // Update pending frame count.
-  mPendingFrameCounts.Put(AsUint64(aWindowId), oldCount - 1);
+  // Update frame counts.
+  count.mPendingCount = count.mPendingCount - 1;
+  count.mRenderingCount = count.mRenderingCount - 1;
+  mPendingFrameCounts.Put(AsUint64(aWindowId), count);
 }
 
 void
 RenderThread::RegisterExternalImage(uint64_t aExternalImageId, already_AddRefed<RenderTextureHost> aTexture)
 {
   MutexAutoLock lock(mRenderTextureMapLock);
 
   if (mHasShutdown) {
@@ -374,16 +405,17 @@ WebRenderThreadPool::~WebRenderThreadPoo
 
 } // namespace wr
 } // namespace mozilla
 
 extern "C" {
 
 void wr_notifier_new_frame_ready(mozilla::wr::WrWindowId aWindowId)
 {
+  mozilla::wr::RenderThread::Get()->IncRenderingFrameCount(aWindowId);
   mozilla::wr::RenderThread::Get()->NewFrameReady(mozilla::wr::WindowId(aWindowId));
 }
 
 void wr_notifier_new_scroll_frame_ready(mozilla::wr::WrWindowId aWindowId, bool aCompositeNeeded)
 {
   // It is not necessary to update rendering with new_scroll_frame_ready.
   // WebRenderBridgeParent::CompositeToTarget() is implemented to call
   // WebRenderAPI::GenerateFrame() if it is necessary to trigger UpdateAndRender().
--- a/gfx/webrender_bindings/RenderThread.h
+++ b/gfx/webrender_bindings/RenderThread.h
@@ -123,20 +123,22 @@ public:
 
   /// Can be called from any thread.
   void UnregisterExternalImage(uint64_t aExternalImageId);
 
   /// Can only be called from the render thread.
   RenderTextureHost* GetRenderTexture(WrExternalImageId aExternalImageId);
 
   /// Can be called from any thread.
-  uint32_t GetPendingFrameCount(wr::WindowId aWindowId);
+  bool TooManyPendingFrames(wr::WindowId aWindowId);
   /// Can be called from any thread.
   void IncPendingFrameCount(wr::WindowId aWindowId);
   /// Can be called from any thread.
+  void IncRenderingFrameCount(wr::WindowId aWindowId);
+  /// Can be called from any thread.
   void DecPendingFrameCount(wr::WindowId aWindowId);
 
   /// Can be called from any thread.
   WebRenderThreadPool& ThreadPool() { return mThreadPool; }
 
 private:
   explicit RenderThread(base::Thread* aThread);
 
@@ -146,18 +148,23 @@ private:
   ~RenderThread();
 
   base::Thread* const mThread;
 
   WebRenderThreadPool mThreadPool;
 
   std::map<wr::WindowId, UniquePtr<RendererOGL>> mRenderers;
 
-  Mutex mPendingFrameCountMapLock;
-  nsDataHashtable<nsUint64HashKey, uint32_t> mPendingFrameCounts;
+  struct FrameCount {
+    int64_t mPendingCount = 0;
+    int64_t mRenderingCount = 0;
+  };
+
+  Mutex mFrameCountMapLock;
+  nsDataHashtable<nsUint64HashKey, FrameCount> mPendingFrameCounts;
 
   Mutex mRenderTextureMapLock;
   nsRefPtrHashtable<nsUint64HashKey, RenderTextureHost> mRenderTextures;
   bool mHasShutdown;
 };
 
 } // namespace wr
 } // namespace mozilla
--- a/ipc/mscom/moz.build
+++ b/ipc/mscom/moz.build
@@ -84,9 +84,9 @@ LOCAL_INCLUDES += [
 
 DEFINES['MOZ_MSCOM_REMARSHAL_NO_HANDLER'] = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 with Files("**"):
-    BUG_COMPONENT = ("Core", "Disability Access APIs")
+    BUG_COMPONENT = ("Core", "IPC: MSCOM")
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -62,17 +62,17 @@ namespace JS {
     D(ALLOC_TRIGGER)                            \
     D(DEBUG_GC)                                 \
     D(COMPARTMENT_REVIVED)                      \
     D(RESET)                                    \
     D(OUT_OF_NURSERY)                           \
     D(EVICT_NURSERY)                            \
     D(DELAYED_ATOMS_GC)                         \
     D(SHARED_MEMORY_LIMIT)                      \
-    D(UNUSED1)                                  \
+    D(IDLE_TIME_COLLECTION)                     \
     D(INCREMENTAL_TOO_SLOW)                     \
     D(ABORT_GC)                                 \
     D(FULL_WHOLE_CELL_BUFFER)                   \
     D(FULL_GENERIC_BUFFER)                      \
     D(FULL_VALUE_BUFFER)                        \
     D(FULL_CELL_PTR_BUFFER)                     \
     D(FULL_SLOT_BUFFER)                         \
     D(FULL_SHAPE_BUFFER)                        \
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -469,25 +469,34 @@ js::TenuringTracer::TenuringTracer(JSRun
 {
 }
 
 inline float
 js::Nursery::calcPromotionRate(bool *validForTenuring) const {
     float used = float(previousGC.nurseryUsedBytes);
     float capacity = float(previousGC.nurseryCapacity);
     float tenured = float(previousGC.tenuredBytes);
+    float rate;
 
-    if (validForTenuring) {
-        /*
-         * We can only use promotion rates if they're likely to be valid,
-         * they're only valid if the nursury was at least 90% full.
-         */
-        *validForTenuring = used > capacity * 0.9f;
+    if (previousGC.nurseryUsedBytes > 0) {
+        if (validForTenuring) {
+            /*
+             * We can only use promotion rates if they're likely to be valid,
+             * they're only valid if the nursury was at least 90% full.
+             */
+            *validForTenuring = used > capacity * 0.9f;
+        }
+        rate = tenured / used;
+    } else {
+        if (validForTenuring)
+            *validForTenuring = false;
+        rate = 0.0f;
     }
-    return tenured / used;
+
+    return rate;
 }
 
 void
 js::Nursery::renderProfileJSON(JSONPrinter& json) const
 {
     if (!isEnabled()) {
         json.beginObject();
         json.property("status", "nursery disabled");
@@ -496,30 +505,32 @@ js::Nursery::renderProfileJSON(JSONPrint
     }
 
     if (previousGC.reason == JS::gcreason::NO_REASON) {
         // If the nursery was empty when the last minorGC was requested, then
         // no nursery collection will have been performed but JSON may still be
         // requested. (And as a public API, this function should not crash in
         // such a case.)
         json.beginObject();
-        json.property("status", "no collection");
+        json.property("status", "nursery empty");
         json.endObject();
         return;
     }
 
     json.beginObject();
 
+    json.property("status", "complete");
+
     json.property("reason", JS::gcreason::ExplainReason(previousGC.reason));
     json.property("bytes_tenured", previousGC.tenuredBytes);
-    json.floatProperty("promotion_rate", calcPromotionRate(nullptr), 0);
-    json.property("nursery_bytes", previousGC.nurseryUsedBytes);
-    json.property("new_nursery_bytes", numChunks() * ChunkSize);
+    json.property("bytes_used", previousGC.nurseryUsedBytes);
+    json.property("cur_capacity", previousGC.nurseryCapacity);
+    json.property("new_capacity", spaceToEnd());
 
-    json.beginObjectProperty("timings");
+    json.beginObjectProperty("phase_times");
 
 #define EXTRACT_NAME(name, text) #name,
     static const char* names[] = {
 FOR_EACH_NURSERY_PROFILE_TIME(EXTRACT_NAME)
 #undef EXTRACT_NAME
     "" };
 
     size_t i = 0;
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -274,22 +274,30 @@ class Nursery
     void* addressOfPosition() const { return (void*)&position_; }
 
     void requestMinorGC(JS::gcreason::Reason reason) const;
 
     bool minorGCRequested() const { return minorGCTriggerReason_ != JS::gcreason::NO_REASON; }
     JS::gcreason::Reason minorGCTriggerReason() const { return minorGCTriggerReason_; }
     void clearMinorGCRequest() { minorGCTriggerReason_ = JS::gcreason::NO_REASON; }
 
+    bool needIdleTimeCollection() const {
+        return minorGCRequested() ||
+               (freeSpace() < kIdleTimeCollectionThreshold);
+    }
+
     bool enableProfiling() const { return enableProfiling_; }
 
   private:
     /* The amount of space in the mapped nursery available to allocations. */
     static const size_t NurseryChunkUsableSize = gc::ChunkSize - gc::ChunkTrailerSize;
 
+    /* Attemp to run a minor GC in the idle time if the free space falls below this threshold. */
+    static constexpr size_t kIdleTimeCollectionThreshold = NurseryChunkUsableSize / 4;
+
     JSRuntime* runtime_;
 
     /* Vector of allocated chunks to allocate from. */
     Vector<NurseryChunk*, 0, SystemAllocPolicy> chunks_;
 
     /* Pointer to the first unallocated byte in the nursery. */
     uintptr_t position_;
 
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -549,29 +549,38 @@ Statistics::renderNurseryJson(JSRuntime*
     JSONPrinter json(printer);
     rt->gc.nursery().renderProfileJSON(json);
     return UniqueChars(printer.release());
 }
 
 UniqueChars
 Statistics::renderJsonMessage(uint64_t timestamp, bool includeSlices) const
 {
+    /*
+     * The format of the JSON message is specified by the GCMajorMarkerPayload
+     * type in perf.html
+     * https://github.com/devtools-html/perf.html/blob/master/src/types/markers.js#L62
+     *
+     * All the properties listed here are created within the timings property
+     * of the GCMajor marker.
+     */
     if (aborted)
         return DuplicateString("{status:\"aborted\"}"); // May return nullptr
 
     Sprinter printer(nullptr, false);
     if (!printer.init())
         return UniqueChars(nullptr);
     JSONPrinter json(printer);
 
     json.beginObject();
+    json.property("status", "completed");
     formatJsonDescription(timestamp, json);
 
     if (includeSlices) {
-        json.beginListProperty("slices");
+        json.beginListProperty("slices_list");
         for (unsigned i = 0; i < slices_.length(); i++)
             formatJsonSlice(i, json);
         json.endList();
     }
 
     json.beginObjectProperty("totals");
     formatJsonPhaseTimes(phaseTimes, json);
     json.endObject();
@@ -605,21 +614,23 @@ Statistics::formatJsonDescription(uint64
     json.property("mmu_50ms", int(mmu50 * 100));
 
     TimeDuration sccTotal, sccLongest;
     sccDurations(&sccTotal, &sccLongest);
     json.property("scc_sweep_total", sccTotal, JSONPrinter::MILLISECONDS);
     json.property("scc_sweep_max_pause", sccLongest, JSONPrinter::MILLISECONDS);
 
     json.property("nonincremental_reason", ExplainAbortReason(nonincrementalReason_));
-    json.property("allocated", uint64_t(preBytes) / 1024 / 1024);
+    json.property("allocated", uint64_t(preBytes)/1024/1024);
+    json.property("allocated_bytes", preBytes);
     json.property("added_chunks", getCount(STAT_NEW_CHUNK));
     json.property("removed_chunks", getCount(STAT_DESTROY_CHUNK));
     json.property("major_gc_number", startingMajorGCNumber);
     json.property("minor_gc_number", startingMinorGCNumber);
+    json.property("slice_number", startingSliceNumber);
 }
 
 void
 Statistics::formatJsonSliceDescription(unsigned i, const SliceData& slice, JSONPrinter& json) const
 {
     TimeDuration when = slice.start - slices_[0].start;
     char budgetDescription[200];
     slice.budget.describe(budgetDescription, sizeof(budgetDescription) - 1);
@@ -875,16 +886,17 @@ Statistics::beginGC(JSGCInvocationKind k
 {
     slices_.clearAndFree();
     sccTimes.clearAndFree();
     gckind = kind;
     nonincrementalReason_ = gc::AbortReason::None;
 
     preBytes = runtime->gc.usage.gcBytes();
     startingMajorGCNumber = runtime->gc.majorGCCount();
+    startingSliceNumber = runtime->gc.gcNumber();
 }
 
 void
 Statistics::endGC()
 {
     TimeDuration sccTotal, sccLongest;
     sccDurations(&sccTotal, &sccLongest);
 
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -312,16 +312,17 @@ struct Statistics
      * threshold and the value that exceeded it. */
     bool thresholdTriggered;
     double triggerAmount;
     double triggerThreshold;
 
     /* GC numbers as of the beginning of the collection. */
     uint64_t startingMinorGCNumber;
     uint64_t startingMajorGCNumber;
+    uint64_t startingSliceNumber;
 
     /* Records the maximum GC pause in an API-controlled interval (in us). */
     mutable TimeDuration maxPauseInInterval;
 
     /* Phases that are currently on stack. */
     Vector<Phase, MAX_PHASE_NESTING, SystemAllocPolicy> phaseStack;
 
     /*
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/sort-update-types.js
@@ -0,0 +1,20 @@
+Object.setPrototypeOf(Array.prototype, {
+    get 0() {
+        Object.setPrototypeOf(Array.prototype, Object.prototype);
+        return "159".repeat(5).substring(2, 5);
+    }
+});
+
+var array = [
+ /*0*/,  1,  2,  3,  4,  5,  6,  7,  8,  9,
+    10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+    20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+    30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
+];
+
+array.sort();
+
+gc();
+
+var r = array[array.length - 1] * 1;
+assertEq(r, 915);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/auto-regress/bug1343513-2.js
@@ -0,0 +1,6 @@
+// |jit-test| error:RangeError
+var i = 0;
+do {
+    i++;
+    var ta = new Int32Array(inIon() ? 0x20000001 : 1);
+} while (!inIon());
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/auto-regress/bug1343513.js
@@ -0,0 +1,6 @@
+// |jit-test| error:RangeError
+var i = 0;
+do {
+    i++;
+    var ta = new Int32Array(inIon() ? 0x7fffffff : 1);
+} while (!inIon());
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1434,16 +1434,30 @@ JS_AddExtraGCRootsTracer(JSContext* cx, 
 }
 
 JS_PUBLIC_API(void)
 JS_RemoveExtraGCRootsTracer(JSContext* cx, JSTraceDataOp traceOp, void* data)
 {
     return cx->runtime()->gc.removeBlackRootsTracer(traceOp, data);
 }
 
+JS_PUBLIC_API(bool)
+JS::IsIdleGCTaskNeeded(JSRuntime* rt) {
+  // Currently, we only collect nursery during idle time.
+  return rt->gc.nursery().needIdleTimeCollection();
+}
+
+JS_PUBLIC_API(void)
+JS::RunIdleTimeGCTask(JSRuntime* rt) {
+  GCRuntime& gc = rt->gc;
+  if (gc.nursery().needIdleTimeCollection()) {
+    gc.minorGC(JS::gcreason::IDLE_TIME_COLLECTION);
+  }
+}
+
 JS_PUBLIC_API(void)
 JS_GC(JSContext* cx)
 {
     AssertHeapIsIdle();
     JS::PrepareForFullGC(cx);
     cx->runtime()->gc.gc(GC_NORMAL, JS::gcreason::API);
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1743,16 +1743,26 @@ JS_AddExtraGCRootsTracer(JSContext* cx, 
 
 /** Undo a call to JS_AddExtraGCRootsTracer. */
 extern JS_PUBLIC_API(void)
 JS_RemoveExtraGCRootsTracer(JSContext* cx, JSTraceDataOp traceOp, void* data);
 
 /*
  * Garbage collector API.
  */
+namespace JS {
+
+extern JS_PUBLIC_API(bool)
+IsIdleGCTaskNeeded(JSRuntime* rt);
+
+extern JS_PUBLIC_API(void)
+RunIdleTimeGCTask(JSRuntime* rt);
+
+} // namespace JS
+
 extern JS_PUBLIC_API(void)
 JS_GC(JSContext* cx);
 
 extern JS_PUBLIC_API(void)
 JS_MaybeGC(JSContext* cx);
 
 extern JS_PUBLIC_API(void)
 JS_SetGCCallback(JSContext* cx, JSGCCallback cb, void* data);
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2202,16 +2202,19 @@ js::intrinsic_ArrayNativeSort(JSContext*
                 if (!MergeSort(vec.begin(), n, vec.begin() + n, SortComparatorInt32s[comp]))
                     return false;
             } else {
                 if (!SortNumerically(cx, &vec, n, comp))
                     return false;
             }
         }
 
+        // We can omit the type update when neither collecting the elements
+        // nor calling the default comparator can execute a (getter) function
+        // that might run user code.
         ShouldUpdateTypes updateTypes = !extraIndexed && (allStrings || allInts)
                                         ? ShouldUpdateTypes::DontUpdate
                                         : ShouldUpdateTypes::Update;
         if (!SetArrayElements(cx, obj, 0, uint32_t(n), vec.begin(), updateTypes))
             return false;
     }
 
     /* Set undefs that sorted after the rest of elements. */
--- a/js/src/tests/Intl/DateTimeFormat/browser.js
+++ b/js/src/tests/Intl/DateTimeFormat/browser.js
@@ -0,0 +1,3 @@
+if (typeof setTimeZone === "undefined") {
+    var setTimeZone = SpecialPowers.Cu.getJSTestingFunctions().setTimeZone;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/DateTimeFormat/tz-environment-variable.js
@@ -0,0 +1,67 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl")||(xulRuntime.OS=="WINNT"&&!xulRuntime.shell)) -- Windows browser in automation doesn't pick up new time zones correctly
+
+// From bug 1330149:
+//
+// Windows only supports a very limited set of IANA time zone names for the TZ
+// environment variable.
+//
+// TZ format supported by Windows: "TZ=tzn[+|-]hh[:mm[:ss]][dzn]".
+//
+// Complete list of all IANA time zone ids matching that format.
+//
+// From tzdata's "northamerica" file:
+//   EST5EDT
+//   CST6CDT
+//   MST7MDT
+//   PST8PDT
+//
+// From tzdata's "backward" file:
+//   GMT+0
+//   GMT-0
+//   GMT0
+//
+// Also supported on Windows even though they don't match the format listed
+// above.
+//
+// From tzdata's "backward" file:
+//   UCT
+//   UTC
+//
+// From tzdata's "etcetera" file:
+//   GMT
+
+function inTimeZone(tzname, fn) {
+    setTimeZone(tzname);
+    try {
+        fn();
+    } finally {
+        setTimeZone("PST8PDT");
+    }
+}
+
+const timeZones = [
+    { id: "EST5EDT" },
+    { id: "CST6CDT" },
+    { id: "MST7MDT" },
+    { id: "PST8PDT" },
+    // ICU on non-Windows platforms doesn't accept these three time zone
+    // identifiers, cf. isValidOlsonID in $ICU/source/common/putil.cpp. We
+    // could add support for them, but it seems unlikely they're used in
+    // practice, so we just skip over them.
+    // { id: "GMT+0", normalized: "UTC" },
+    // { id: "GMT-0", normalized: "UTC" },
+    // { id: "GMT0", normalized: "UTC" },
+    { id: "UCT", normalized: "Etc/UCT" },
+    { id: "UTC", normalized: "UTC" },
+    { id: "GMT", normalized: "UTC" },
+];
+
+for (let {id, normalized = id} of timeZones) {
+    inTimeZone(id, () => {
+        let opts = new Intl.DateTimeFormat().resolvedOptions();
+        assertEq(opts.timeZone, normalized);
+    });
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0, "ok");
--- a/js/src/tests/ecma_3/Date/15.9.5.5.js
+++ b/js/src/tests/ecma_3/Date/15.9.5.5.js
@@ -1,9 +1,9 @@
-// |reftest| random-if(xulRuntime.OS=="Linux") skip-if(xulRuntime.OS=="WINNT")
+// |reftest| random-if(xulRuntime.OS=="Linux")
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 /**
    File Name:          15.9.5.5.js
--- a/js/src/tests/ecma_3/Date/15.9.5.6.js
+++ b/js/src/tests/ecma_3/Date/15.9.5.6.js
@@ -1,9 +1,8 @@
-// |reftest| skip-if(xulRuntime.OS=="WINNT")
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 /**
    File Name:          15.9.5.6.js
--- a/js/src/tests/ecma_3/Date/15.9.5.7.js
+++ b/js/src/tests/ecma_3/Date/15.9.5.7.js
@@ -1,9 +1,8 @@
-// |reftest| skip-if(xulRuntime.OS=="WINNT")
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
    File Name:    15.9.5.7.js
    ECMA Section: 15.9.5.7 Date.prototype.toLocaleTimeString()
--- a/js/src/tests/ecma_6/Date/time-zones-posix.js
+++ b/js/src/tests/ecma_6/Date/time-zones-posix.js
@@ -17,16 +17,27 @@
 //   CST6CDT
 //   MST7MDT
 //   PST8PDT
 //
 // From tzdata's "backward" file:
 //   GMT+0
 //   GMT-0
 //   GMT0
+//
+// Also supported on Windows even though they don't match the format listed
+// above.
+//
+// From tzdata's "backward" file:
+//   UCT
+//   UTC
+//
+// From tzdata's "etcetera" file:
+//   GMT
+
 
 // Perform the following replacements:
 //   America/New_York    -> EST5EDT
 //   America/Chicago     -> CST6CDT
 //   America/Denver      -> MST7MDT
 //   America/Los_Angeles -> PST8PDT
 //
 // And remove any tests not matching one of the four time zones from above.
--- a/js/src/vm/DateTime.cpp
+++ b/js/src/vm/DateTime.cpp
@@ -1,25 +1,34 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "vm/DateTime.h"
 
+#if defined(XP_WIN)
+#include "mozilla/UniquePtr.h"
+
+#include <cstdlib>
+#include <cstring>
+#endif /* defined(XP_WIN) */
 #include <time.h>
 
 #include "jsutil.h"
 
 #include "js/Date.h"
 #include "threading/ExclusiveData.h"
 #if ENABLE_INTL_API
 #include "unicode/timezone.h"
+#if defined(XP_WIN)
+#include "unicode/unistr.h"
 #endif
+#endif /* ENABLE_INTL_API */
 #include "vm/MutexIDs.h"
 
 using mozilla::UnspecifiedNaN;
 
 static bool
 ComputeLocalTime(time_t local, struct tm* ptm)
 {
 #if defined(_WIN32)
@@ -329,19 +338,93 @@ JS::ResetTimeZone()
 {
     js::DateTimeInfo::updateTimeZoneAdjustment();
 
 #if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT)
     js::IcuTimeZoneState->lock().get() = js::IcuTimeZoneStatus::NeedsUpdate;
 #endif
 }
 
+#if defined(XP_WIN)
+static bool
+IsOlsonCompatibleWindowsTimeZoneId(const char* tz)
+{
+    // ICU ignores the TZ environment variable on Windows and instead directly
+    // invokes Win API functions to retrieve the current time zone. But since
+    // we're still using the POSIX-derived localtime_s() function on Windows
+    // and localtime_s() does return a time zone adjusted value based on the
+    // TZ environment variable, we need to manually adjust the default ICU
+    // time zone if TZ is set.
+    //
+    // Windows supports the following format for TZ: tzn[+|-]hh[:mm[:ss]][dzn]
+    // where "tzn" is the time zone name for standard time, the time zone
+    // offset is positive for time zones west of GMT, and "dzn" is the
+    // optional time zone name when daylight savings are observed. Daylight
+    // savings are always based on the U.S. daylight saving rules, that means
+    // for example it's not possible to use "TZ=CET-1CEST" to select the IANA
+    // time zone "CET".
+    //
+    // When comparing this restricted format for TZ to all IANA time zone
+    // names, the following time zones are in the intersection of what's
+    // supported by Windows and is also a valid IANA time zone identifier.
+    //
+    // Even though the time zone offset is marked as mandatory on MSDN, it
+    // appears it defaults to zero when omitted. This in turn means we can
+    // also allow the time zone identifiers "UCT", "UTC", and "GMT".
+
+    static const char* const allowedIds[] = {
+        // From tzdata's "northamerica" file:
+        "EST5EDT",
+        "CST6CDT",
+        "MST7MDT",
+        "PST8PDT",
+
+        // From tzdata's "backward" file:
+        "GMT+0",
+        "GMT-0",
+        "GMT0",
+        "UCT",
+        "UTC",
+
+        // From tzdata's "etcetera" file:
+        "GMT",
+    };
+    for (const auto& allowedId : allowedIds) {
+        if (std::strcmp(allowedId, tz) == 0)
+            return true;
+    }
+    return false;
+}
+#endif
+
 void
 js::ResyncICUDefaultTimeZone()
 {
 #if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT)
     auto guard = IcuTimeZoneState->lock();
     if (guard.get() == IcuTimeZoneStatus::NeedsUpdate) {
-        icu::TimeZone::recreateDefault();
+        bool recreate = true;
+#if defined(XP_WIN)
+        // If TZ is set and its value is valid under Windows' and IANA's time
+        // zone identifier rules, update the ICU default time zone to use this
+        // value.
+        const char* tz = std::getenv("TZ");
+        if (tz && IsOlsonCompatibleWindowsTimeZoneId(tz)) {
+            icu::UnicodeString tzid(tz, -1, US_INV);
+            mozilla::UniquePtr<icu::TimeZone> newTimeZone(icu::TimeZone::createTimeZone(tzid));
+            MOZ_ASSERT(newTimeZone);
+            if (*newTimeZone != icu::TimeZone::getUnknown()) {
+                // adoptDefault() takes ownership of the time zone.
+                icu::TimeZone::adoptDefault(newTimeZone.release());
+                recreate = false;
+            }
+        } else {
+            // If |tz| isn't a supported time zone identifier, use the default
+            // Windows time zone for ICU.
+            // TODO: Handle invalid time zone identifiers (bug 342068).
+        }
+#endif
+        if (recreate)
+            icu::TimeZone::recreateDefault();
         guard.get() = IcuTimeZoneStatus::Valid;
     }
 #endif
 }
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -3163,17 +3163,17 @@ Preferences::HandleDirty()
     sPreferences->mDirty = true;
 
     if (sPreferences->mCurrentFile && sPreferences->AllowOffMainThreadSave() &&
         !sPreferences->mSavePending) {
       sPreferences->mSavePending = true;
       static const int PREF_DELAY_MS = 500;
       NS_DelayedDispatchToCurrentThread(
         mozilla::NewRunnableMethod("Preferences::SavePrefFileAsynchronous",
-                                   sPreferences,
+                                   sPreferences.get(),
                                    &Preferences::SavePrefFileAsynchronous),
         PREF_DELAY_MS);
     }
   }
 }
 
 static nsresult
 openPrefFile(nsIFile* aFile);
@@ -3206,19 +3206,17 @@ static const char kPrefFileHeader[] =
   " * To make a manual change to preferences, you can visit the URL "
   "about:config"
   NS_LINEBREAK
   " */"
   NS_LINEBREAK
   NS_LINEBREAK;
 // clang-format on
 
-Preferences* Preferences::sPreferences = nullptr;
-nsIPrefBranch* Preferences::sRootBranch = nullptr;
-nsIPrefBranch* Preferences::sDefaultRootBranch = nullptr;
+StaticRefPtr<Preferences> Preferences::sPreferences;
 bool Preferences::sShutdown = false;
 
 // This globally enables or disables OMT pref writing, both sync and async.
 static int32_t sAllowOMTPrefWrite = -1;
 
 class ValueObserverHashKey : public PLDHashEntryHdr
 {
 public:
@@ -3541,23 +3539,23 @@ Preferences::SizeOfIncludingThisAndOther
   if (gObserverTable) {
     n += gObserverTable->ShallowSizeOfIncludingThis(aMallocSizeOf);
     for (auto iter = gObserverTable->Iter(); !iter.Done(); iter.Next()) {
       n += iter.Key()->mPrefName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
       n += iter.Data()->mClosures.ShallowSizeOfExcludingThis(aMallocSizeOf);
     }
   }
 
-  if (sRootBranch) {
-    n += reinterpret_cast<nsPrefBranch*>(sRootBranch)
+  if (sPreferences->mRootBranch) {
+    n += static_cast<nsPrefBranch*>(sPreferences->mRootBranch.get())
            ->SizeOfIncludingThis(aMallocSizeOf);
   }
 
-  if (sDefaultRootBranch) {
-    n += reinterpret_cast<nsPrefBranch*>(sDefaultRootBranch)
+  if (sPreferences->mDefaultRootBranch) {
+    n += static_cast<nsPrefBranch*>(sPreferences->mDefaultRootBranch.get())
            ->SizeOfIncludingThis(aMallocSizeOf);
   }
 
   n += pref_SizeOfPrivateData(aMallocSizeOf);
 
   return n;
 }
 
@@ -3704,29 +3702,21 @@ Preferences::GetInstanceForService()
     return do_AddRef(sPreferences);
   }
 
   if (sShutdown) {
     gCacheDataDesc = "shutting down in GetInstanceForService()";
     return nullptr;
   }
 
-  sRootBranch = new nsPrefBranch("", false);
-  NS_ADDREF(sRootBranch);
-  sDefaultRootBranch = new nsPrefBranch("", true);
-  NS_ADDREF(sDefaultRootBranch);
-
   sPreferences = new Preferences();
-  NS_ADDREF(sPreferences);
-
   Result<Ok, const char*> res = sPreferences->Init();
   if (res.isErr()) {
-    // The singleton instance will delete sRootBranch and sDefaultRootBranch.
+    sPreferences = nullptr;
     gCacheDataDesc = res.unwrapErr();
-    NS_RELEASE(sPreferences);
     return nullptr;
   }
 
   gCacheData = new nsTArray<nsAutoPtr<CacheData>>();
   gCacheDataDesc = "set by GetInstanceForService()";
 
   gObserverTable = new nsRefPtrHashtable<ValueObserverHashKey, ValueObserver>();
 
@@ -3761,49 +3751,42 @@ Preferences::InitStaticMembers()
   return sPreferences != nullptr;
 }
 
 /* static */ void
 Preferences::Shutdown()
 {
   if (!sShutdown) {
     sShutdown = true; // Don't create the singleton instance after here.
-
-    // Don't set sPreferences to nullptr here. The instance may be grabbed by
-    // other modules. The utility methods of Preferences should be available
-    // until the singleton instance actually released.
-    if (sPreferences) {
-      sPreferences->Release();
-    }
+    sPreferences = nullptr;
   }
 }
 
 //-----------------------------------------------------------------------------
 
 //
 // Constructor/Destructor
 //
 
-Preferences::Preferences() = default;
+Preferences::Preferences()
+  : mRootBranch(new nsPrefBranch("", false))
+  , mDefaultRootBranch(new nsPrefBranch("", true))
+{
+}
 
 Preferences::~Preferences()
 {
-  NS_ASSERTION(sPreferences == this, "Isn't this the singleton instance?");
+  MOZ_ASSERT(!sPreferences);
 
   delete gObserverTable;
   gObserverTable = nullptr;
 
   delete gCacheData;
   gCacheData = nullptr;
 
-  NS_RELEASE(sRootBranch);
-  NS_RELEASE(sDefaultRootBranch);
-
-  sPreferences = nullptr;
-
   PREF_Cleanup();
 }
 
 //
 // nsISupports Implementation
 //
 
 NS_IMPL_ADDREF(Preferences)
@@ -4165,28 +4148,28 @@ Preferences::GetBranch(const char* aPref
 {
   if ((nullptr != aPrefRoot) && (*aPrefRoot != '\0')) {
     // TODO: Cache this stuff and allow consumers to share branches (hold weak
     // references, I think).
     RefPtr<nsPrefBranch> prefBranch = new nsPrefBranch(aPrefRoot, false);
     prefBranch.forget(aRetVal);
   } else {
     // Special case: caching the default root.
-    nsCOMPtr<nsIPrefBranch> root(sRootBranch);
+    nsCOMPtr<nsIPrefBranch> root(sPreferences->mRootBranch);
     root.forget(aRetVal);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Preferences::GetDefaultBranch(const char* aPrefRoot, nsIPrefBranch** aRetVal)
 {
   if (!aPrefRoot || !aPrefRoot[0]) {
-    nsCOMPtr<nsIPrefBranch> root(sDefaultRootBranch);
+    nsCOMPtr<nsIPrefBranch> root(sPreferences->mDefaultRootBranch);
     root.forget(aRetVal);
     return NS_OK;
   }
 
   // TODO: Cache this stuff and allow consumers to share branches (hold weak
   // references, I think).
   RefPtr<nsPrefBranch> prefBranch = new nsPrefBranch(aPrefRoot, true);
   if (!prefBranch) {
@@ -4860,30 +4843,30 @@ Preferences::GetLocalizedCString(const c
   return rv;
 }
 
 /* static */ nsresult
 Preferences::GetLocalizedString(const char* aPref, nsAString& aResult)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   nsCOMPtr<nsIPrefLocalizedString> prefLocalString;
-  nsresult rv = sRootBranch->GetComplexValue(
+  nsresult rv = sPreferences->mRootBranch->GetComplexValue(
     aPref, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(prefLocalString));
   if (NS_SUCCEEDED(rv)) {
     NS_ASSERTION(prefLocalString, "Succeeded but the result is NULL");
     prefLocalString->GetData(aResult);
   }
   return rv;
 }
 
 /* static */ nsresult
 Preferences::GetComplex(const char* aPref, const nsIID& aType, void** aResult)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
-  return sRootBranch->GetComplexValue(aPref, aType, aResult);
+  return sPreferences->mRootBranch->GetComplexValue(aPref, aType, aResult);
 }
 
 /* static */ nsresult
 Preferences::SetCString(const char* aPref, const char* aValue)
 {
   ENSURE_MAIN_PROCESS_WITH_WARNING("SetCString", aPref);
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   return PREF_SetCStringPref(aPref, nsDependentCString(aValue), false);
@@ -4936,17 +4919,17 @@ Preferences::SetFloat(const char* aPref,
 }
 
 /* static */ nsresult
 Preferences::SetComplex(const char* aPref,
                         const nsIID& aType,
                         nsISupports* aValue)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
-  return sRootBranch->SetComplexValue(aPref, aType, aValue);
+  return sPreferences->mRootBranch->SetComplexValue(aPref, aType, aValue);
 }
 
 /* static */ nsresult
 Preferences::ClearUser(const char* aPref)
 {
   ENSURE_MAIN_PROCESS_WITH_WARNING("ClearUser", aPref);
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   return PREF_ClearUserPref(aPref);
@@ -4959,46 +4942,46 @@ Preferences::HasUserValue(const char* aP
   return PREF_HasUserPref(aPref);
 }
 
 /* static */ int32_t
 Preferences::GetType(const char* aPref)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID);
   int32_t result;
-  return NS_SUCCEEDED(sRootBranch->GetPrefType(aPref, &result))
+  return NS_SUCCEEDED(sPreferences->mRootBranch->GetPrefType(aPref, &result))
            ? result
            : nsIPrefBranch::PREF_INVALID;
 }
 
 /* static */ nsresult
 Preferences::AddStrongObserver(nsIObserver* aObserver, const char* aPref)
 {
   MOZ_ASSERT(aObserver);
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
-  return sRootBranch->AddObserver(aPref, aObserver, false);
+  return sPreferences->mRootBranch->AddObserver(aPref, aObserver, false);
 }
 
 /* static */ nsresult
 Preferences::AddWeakObserver(nsIObserver* aObserver, const char* aPref)
 {
   MOZ_ASSERT(aObserver);
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
-  return sRootBranch->AddObserver(aPref, aObserver, true);
+  return sPreferences->mRootBranch->AddObserver(aPref, aObserver, true);
 }
 
 /* static */ nsresult
 Preferences::RemoveObserver(nsIObserver* aObserver, const char* aPref)
 {
   MOZ_ASSERT(aObserver);
   if (!sPreferences && sShutdown) {
     return NS_OK; // Observers have been released automatically.
   }
   NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
-  return sRootBranch->RemoveObserver(aPref, aObserver);
+  return sPreferences->mRootBranch->RemoveObserver(aPref, aObserver);
 }
 
 /* static */ nsresult
 Preferences::AddStrongObservers(nsIObserver* aObserver, const char** aPrefs)
 {
   MOZ_ASSERT(aObserver);
   for (uint32_t i = 0; aPrefs[i]; i++) {
     nsresult rv = AddStrongObserver(aObserver, aPrefs[i]);
@@ -5335,40 +5318,42 @@ Preferences::GetDefaultLocalizedCString(
   return rv;
 }
 
 /* static */ nsresult
 Preferences::GetDefaultLocalizedString(const char* aPref, nsAString& aResult)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   nsCOMPtr<nsIPrefLocalizedString> prefLocalString;
-  nsresult rv = sDefaultRootBranch->GetComplexValue(
+  nsresult rv = sPreferences->mDefaultRootBranch->GetComplexValue(
     aPref, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(prefLocalString));
   if (NS_SUCCEEDED(rv)) {
     NS_ASSERTION(prefLocalString, "Succeeded but the result is NULL");
     prefLocalString->GetData(aResult);
   }
   return rv;
 }
 
 /* static */ nsresult
 Preferences::GetDefaultComplex(const char* aPref,
                                const nsIID& aType,
                                void** aResult)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
-  return sDefaultRootBranch->GetComplexValue(aPref, aType, aResult);
+  return sPreferences->mDefaultRootBranch->GetComplexValue(
+    aPref, aType, aResult);
 }
 
 /* static */ int32_t
 Preferences::GetDefaultType(const char* aPref)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID);
   int32_t result;
-  return NS_SUCCEEDED(sDefaultRootBranch->GetPrefType(aPref, &result))
+  return NS_SUCCEEDED(
+           sPreferences->mDefaultRootBranch->GetPrefType(aPref, &result))
            ? result
            : nsIPrefBranch::PREF_INVALID;
 }
 
 } // namespace mozilla
 
 #undef ENSURE_MAIN_PROCESS
 #undef ENSURE_MAIN_PROCESS_WITH_WARNING
--- a/modules/libpref/Preferences.h
+++ b/modules/libpref/Preferences.h
@@ -8,16 +8,17 @@
 #define mozilla_Preferences_h
 
 #ifndef MOZILLA_INTERNAL_API
 #error "This header is only usable from within libxul (MOZILLA_INTERNAL_API)."
 #endif
 
 #include "mozilla/Atomics.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/StaticPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
 #include "nsIPrefBranch.h"
 #include "nsIPrefService.h"
 #include "nsTArray.h"
 #include "nsWeakReference.h"
 
 class nsIFile;
@@ -59,17 +60,17 @@ class Preferences final
   , public nsIPrefBranch
   , public nsSupportsWeakReference
 {
 public:
   typedef mozilla::dom::PrefSetting PrefSetting;
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIPREFSERVICE
-  NS_FORWARD_NSIPREFBRANCH(sRootBranch->)
+  NS_FORWARD_NSIPREFBRANCH(mRootBranch->)
   NS_DECL_NSIOBSERVER
 
   Preferences();
 
   mozilla::Result<Ok, const char*> Init();
 
   // Returns true if the Preferences service is available, false otherwise.
   static bool IsServiceAvailable();
@@ -89,24 +90,24 @@ public:
     NS_ENSURE_TRUE(InitStaticMembers(), nullptr);
     return sPreferences;
   }
 
   // Returns shared pref branch instance. NOTE: not addreffed.
   static nsIPrefBranch* GetRootBranch()
   {
     NS_ENSURE_TRUE(InitStaticMembers(), nullptr);
-    return sRootBranch;
+    return sPreferences->mRootBranch;
   }
 
   // Returns shared default pref branch instance. NOTE: not addreffed.
   static nsIPrefBranch* GetDefaultRootBranch()
   {
     NS_ENSURE_TRUE(InitStaticMembers(), nullptr);
-    return sDefaultRootBranch;
+    return sPreferences->mDefaultRootBranch;
   }
 
   // Gets int or bool type pref value with default value if failed to get the
   // pref.
   static bool GetBool(const char* aPref, bool aDefault = false)
   {
     bool result = aDefault;
     GetBool(aPref, &result);
@@ -398,19 +399,20 @@ protected:
 private:
   nsCOMPtr<nsIFile> mCurrentFile;
   bool mDirty = false;
   bool mProfileShutdown = false;
   // We wait a bit after prefs are dirty before writing them. In this period,
   // mDirty and mSavePending will both be true.
   bool mSavePending = false;
 
-  static Preferences* sPreferences;
-  static nsIPrefBranch* sRootBranch;
-  static nsIPrefBranch* sDefaultRootBranch;
+  nsCOMPtr<nsIPrefBranch> mRootBranch;
+  nsCOMPtr<nsIPrefBranch> mDefaultRootBranch;
+
+  static StaticRefPtr<Preferences> sPreferences;
   static bool sShutdown;
 
   // Init static members. Returns true on success.
   static bool InitStaticMembers();
 };
 
 } // namespace mozilla
 
--- a/netwerk/cache/nsCacheService.cpp
+++ b/netwerk/cache/nsCacheService.cpp
@@ -3059,18 +3059,21 @@ void nsCacheService::GetAppCacheDirector
 
     directory.forget(result);
 }
 
 
 void
 nsCacheService::LogCacheStatistics()
 {
-    uint32_t hitPercentage = (uint32_t)((((double)mCacheHits) /
-        ((double)(mCacheHits + mCacheMisses))) * 100);
+    uint32_t hitPercentage = 0;
+    if (!mCacheHits || !mCacheMisses) {
+        hitPercentage = (uint32_t)((((double)mCacheHits) /
+            ((double)(mCacheHits + mCacheMisses))) * 100);
+    }
     CACHE_LOG_INFO(("\nCache Service Statistics:\n\n"));
     CACHE_LOG_INFO(("    TotalEntries   = %d\n", mTotalEntries));
     CACHE_LOG_INFO(("    Cache Hits     = %d\n", mCacheHits));
     CACHE_LOG_INFO(("    Cache Misses   = %d\n", mCacheMisses));
     CACHE_LOG_INFO(("    Cache Hit %%    = %d%%\n", hitPercentage));
     CACHE_LOG_INFO(("    Max Key Length = %d\n", mMaxKeyLength));
     CACHE_LOG_INFO(("    Max Meta Size  = %d\n", mMaxMetaSize));
     CACHE_LOG_INFO(("    Max Data Size  = %d\n", mMaxDataSize));
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_responseBody.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_responseBody.html
@@ -182,34 +182,89 @@ const TASKS = [
           checkState("suspended");
 
           await new Promise(resolve => setTimeout(resolve, TIMEOUT * 3));
 
           checkState("suspended");
           filter.disconnect();
           checkState("disconnected");
 
-          for (let method of ["suspend", "resume", "close"]) {
+          for (let method of ["suspend", "resume", "close", "disconnect"]) {
             browser.test.assertThrows(
               () => {
                 filter[method]();
               },
               /.*/,
               `(${num}): ${method}() should throw while disconnected`);
           }
 
           browser.test.assertThrows(
             () => {
               filter.write(encoder.encode("Foo bar"));
             },
             /.*/,
             `(${num}): write() should throw while disconnected`);
 
+          resolve();
+        }
+      };
+
+      filter.onerror = event => {
+        browser.test.fail(`(${num}): Got unexpected error event: ${filter.error}`);
+      };
+    },
+    verify(response) {
+      is(response, PARTS.join(""), "Got expected final HTML");
+    },
+  },
+  {
+    url: "slow_response.sjs",
+    task(filter, resolve, num) {
+      let encoder = new TextEncoder("utf-8");
+
+      filter.onstop = event => {
+        browser.test.fail(`(${num}): Got unexpected onStop event while disconnected`);
+      };
+
+      let n = 0;
+      filter.ondata = async event => {
+        n++;
+
+        filter.write(event.data);
+
+        if (n == 3) {
+          filter.suspend();
+          await new Promise(resolve => setTimeout(resolve, TIMEOUT));
+          filter.resume();
+          filter.suspend();
+
+          await new Promise(resolve => setTimeout(resolve, TIMEOUT));
+
+          filter.resume();
+          await new Promise(resolve => setTimeout(resolve, 0));
+          filter.suspend();
+
           filter.disconnect();
 
+          for (let method of ["suspend", "resume", "close", "disconnect"]) {
+            browser.test.assertThrows(
+              () => {
+                filter[method]();
+              },
+              /.*/,
+              `(${num}): ${method}() should throw while disconnected`);
+          }
+
+          browser.test.assertThrows(
+            () => {
+              filter.write(encoder.encode("Foo bar"));
+            },
+            /.*/,
+            `(${num}): write() should throw while disconnected`);
+
           resolve();
         }
       };
 
       filter.onerror = event => {
         browser.test.fail(`(${num}): Got unexpected error event: ${filter.error}`);
       };
     },
--- a/toolkit/components/extensions/webrequest/StreamFilterChild.cpp
+++ b/toolkit/components/extensions/webrequest/StreamFilterChild.cpp
@@ -108,16 +108,30 @@ StreamFilterChild::Resume(ErrorResult& a
 
     default:
       aRv.Throw(NS_ERROR_FAILURE);
       return;
     }
     break;
 
   case State::Resuming:
+    switch (mNextState) {
+    case State::Suspending:
+      mNextState = State::Resuming;
+      break;
+
+    case State::TransferringData:
+      break;
+
+    default:
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
+    }
+    break;
+
   case State::TransferringData:
     break;
 
   default:
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
@@ -137,31 +151,28 @@ StreamFilterChild::Disconnect(ErrorResul
     WriteBufferedData();
     SendDisconnect();
     break;
 
   case State::Suspending:
   case State::Resuming:
     switch (mNextState) {
     case State::Suspended:
+    case State::Suspending:
     case State::Resuming:
-    case State::Disconnecting:
+    case State::TransferringData:
       mNextState = State::Disconnecting;
       break;
 
     default:
       aRv.Throw(NS_ERROR_FAILURE);
       return;
     }
     break;
 
-  case State::Disconnecting:
-  case State::Disconnected:
-    break;
-
   default:
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 }
 
 void
 StreamFilterChild::Close(ErrorResult& aRv)
@@ -173,17 +184,28 @@ StreamFilterChild::Close(ErrorResult& aR
     mState = State::Closing;
     mNextState = State::Closed;
 
     SendClose();
     break;
 
   case State::Suspending:
   case State::Resuming:
-    mNextState = State::Closing;
+    switch (mNextState) {
+    case State::Suspended:
+    case State::Suspending:
+    case State::Resuming:
+    case State::TransferringData:
+      mNextState = State::Closing;
+      break;
+
+    default:
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
+    }
     break;
 
   case State::Closing:
     MOZ_DIAGNOSTIC_ASSERT(mNextState == State::Closed);
     break;
 
   case State::Closed:
     break;
@@ -218,16 +240,18 @@ StreamFilterChild::SetNextState()
 
   case State::Closing:
     mNextState = State::Closed;
     SendClose();
     break;
 
   case State::Disconnecting:
     mNextState = State::Disconnected;
+
+    WriteBufferedData();
     SendDisconnect();
     break;
 
   case State::FinishedTransferringData:
     if (mStreamFilter) {
       mStreamFilter->FireEvent(NS_LITERAL_STRING("stop"));
       // We don't need access to the stream filter after this point, so break our
       // reference cycle, so that it can be collected if we're the last reference.
--- a/toolkit/components/telemetry/GCTelemetry.jsm
+++ b/toolkit/components/telemetry/GCTelemetry.jsm
@@ -47,18 +47,18 @@ class GCData {
   // milliseconds since startup.
   rebaseTimes(data) {
     function fixup(t) {
       return t / 1000.0 - BASE_TIME;
     }
 
     data.timestamp = fixup(data.timestamp);
 
-    for (let i = 0; i < data.slices.length; i++) {
-      let slice = data.slices[i];
+    for (let i = 0; i < data.slices_list.length; i++) {
+      let slice = data.slices_list[i];
       slice.start_timestamp = fixup(slice.start_timestamp);
       slice.end_timestamp = fixup(slice.end_timestamp);
     }
   }
 
   // Records a GC (represented by |data|) in the randomlySelected or
   // worst batches depending on the criteria above.
   record(data) {
@@ -105,53 +105,53 @@ class GCData {
     };
   }
 }
 
 // If you adjust any of the constants here (slice limit, number of keys, etc.)
 // make sure to update the JSON schema at:
 // https://github.com/mozilla-services/mozilla-pipeline-schemas/blob/master/telemetry/main.schema.json
 // You should also adjust browser_TelemetryGC.js.
-const MAX_GC_KEYS = 25;
+const MAX_GC_KEYS = 30;
 const MAX_SLICES = 4;
 const MAX_SLICE_KEYS = 15;
 const MAX_PHASES = 65;
 
 function limitProperties(obj, count) {
   // If there are too many properties, just delete them all. We don't
   // expect this ever to happen.
   if (Object.keys(obj).length > count) {
     for (let key of Object.keys(obj)) {
       delete obj[key];
     }
   }
 }
 
 function limitSize(data) {
   // Store the number of slices so we know if we lost any at the end.
-  data.num_slices = data.slices.length;
+  data.num_slices = data.slices_list.length;
 
-  data.slices.sort((a, b) => b.pause - a.pause);
+  data.slices_list.sort((a, b) => b.pause - a.pause);
 
-  if (data.slices.length > MAX_SLICES) {
+  if (data.slices_list.length > MAX_SLICES) {
     // Make sure we always keep the first slice since it has the
     // reason the GC was started.
-    let firstSliceIndex = data.slices.findIndex(s => s.slice == 0);
+    let firstSliceIndex = data.slices_list.findIndex(s => s.slice == 0);
     if (firstSliceIndex >= MAX_SLICES) {
-      data.slices[MAX_SLICES - 1] = data.slices[firstSliceIndex];
+      data.slices_list[MAX_SLICES - 1] = data.slices_list[firstSliceIndex];
     }
 
-    data.slices.length = MAX_SLICES;
+    data.slices_list.length = MAX_SLICES;
   }
 
-  data.slices.sort((a, b) => a.slice - b.slice);
+  data.slices_list.sort((a, b) => a.slice - b.slice);
 
   limitProperties(data, MAX_GC_KEYS);
 
-  for (let slice of data.slices) {
+  for (let slice of data.slices_list) {
     limitProperties(slice, MAX_SLICE_KEYS);
     limitProperties(slice.times, MAX_PHASES);
   }
 
   limitProperties(data.totals, MAX_PHASES);
 }
 
 let processData = new Map();
--- a/toolkit/components/telemetry/docs/data/main-ping.rst
+++ b/toolkit/components/telemetry/docs/data/main-ping.rst
@@ -462,16 +462,18 @@ GC has a C/T probablility of being selec
 
 Structure:
 
 .. code-block:: js
 
     "gc": {
       "random": [
         {
+          // "completed" or "aborted" if an OOM occured.
+          "status": "completed",
           // Timestamps are in milliseconds since startup. All the times here
           // are wall-clock times, which may not be monotonically increasing.
           "timestamp": 294872.2,
           // All durations are in milliseconds.
           "max_pause": 73.629,
           "total_time": 364.951, // Sum of all slice times.
           "zones_collected": 9,
           "total_zones": 9,
@@ -481,25 +483,27 @@ Structure:
           "mmu_20ms": 0,
           "mmu_50ms": 0,
           // Reasons include "None", "NonIncrementalRequested",
           // "AbortRequested", "KeepAtomsSet", "IncrementalDisabled",
           // "ModeChange", "MallocBytesTrigger", "GCBytesTrigger",
           // "ZoneChange", "CompartmentRevived".
           "nonincremental_reason": "None",
           "allocated": 37, // In megabytes.
+          "allocated_bytes": 38853696 // in bytes
           "added_chunks": 54,
           "removed_chunks": 12,
           // Total number of slices (some of which may not appear
           // in the "slices" array).
-          "num_slices": 15,
+          "slices": 15,
           // We record at most 4 slices.
-          "slices": [
+          "slice_number": 218, // The first slice number for this GC event.
+          "slices_list": [
             {
-              "slice": 0,  // The index of this slice.
+              "slice": 218,  // The global index of this slice.
               "pause": 23.221,  // How long the slice took.
               "when": 0,  // Milliseconds since the start of the GC.
               "reason": "SET_NEW_DOCUMENT",
               // GC state when the slice started
               "initial_state": "NotActive",
               // GC state when the slice ended
               "final_state": "Mark",
               // Budget is either "Xms", "work(Y)", or
--- a/toolkit/components/telemetry/tests/browser/browser_TelemetryGC.js
+++ b/toolkit/components/telemetry/tests/browser/browser_TelemetryGC.js
@@ -32,70 +32,72 @@ function check(entries) {
   let foundGCs = 0;
 
   for (let f of FIELDS) {
     ok(Array.isArray(entries[f]), "have an array of GCs");
 
     ok(entries[f].length <= 2, "not too many GCs");
 
     for (let gc of entries[f]) {
-      ok(gc !== null, "GC is non-null");
+      isnot(gc, null, "GC is non-null");
 
       foundGCs++;
 
-      ok(Object.keys(gc).length <= 25, "number of keys in GC is not too large");
+      ok(Object.keys(gc).length <= 30, "number of keys in GC is not too large");
 
       // Sanity check the GC data.
+      ok("status" in gc, "status field present");
+      is(gc.status, "completed", "status field correct");
       ok("total_time" in gc, "total_time field present");
       ok("max_pause" in gc, "max_pause field present");
 
-      ok("slices" in gc, "slices field present");
-      ok(Array.isArray(gc.slices), "slices is an array");
-      ok(gc.slices.length > 0, "slices array non-empty");
-      ok(gc.slices.length <= 4, "slices array is not too long");
+      ok("slices_list" in gc, "slices_list field present");
+      ok(Array.isArray(gc.slices_list), "slices_list is an array");
+      ok(gc.slices_list.length > 0, "slices_list array non-empty");
+      ok(gc.slices_list.length <= 4, "slices_list array is not too long");
 
       ok("totals" in gc, "totals field present");
-      ok(typeof(gc.totals) == "object", "totals is an object");
+      is(typeof(gc.totals), "object", "totals is an object");
       ok(Object.keys(gc.totals).length <= 65, "totals array is not too long");
 
       // Make sure we don't skip any big objects.
       for (let key in gc) {
-        if (key != "slices" && key != "totals") {
-          ok(typeof(gc[key]) != "object", `${key} property should be primitive`);
+        if (key != "slices_list" && key != "totals") {
+          isnot(typeof(gc[key]), "object", `${key} property should be primitive`);
         }
       }
 
       let phases = new Set();
 
-      for (let slice of gc.slices) {
+      for (let slice of gc.slices_list) {
         ok(Object.keys(slice).length <= 15, "slice is not too large");
 
         ok("pause" in slice, "pause field present in slice");
         ok("reason" in slice, "reason field present in slice");
         ok("times" in slice, "times field present in slice");
 
         // Make sure we don't skip any big objects.
         for (let key in slice) {
           if (key != "times") {
-            ok(typeof(slice[key]) != "object", `${key} property should be primitive`);
+            isnot(typeof(slice[key]), "object", `${key} property should be primitive`);
           }
         }
 
         ok(Object.keys(slice.times).length <= 65, "no more than 65 phases");
 
         for (let phase in slice.times) {
           phases.add(phase);
-          ok(typeof(slice.times[phase]) == "number", `${phase} property should be a number`);
+          is(typeof(slice.times[phase]), "number", `${phase} property should be a number`);
         }
       }
 
       let totals = gc.totals;
       // Make sure we don't skip any big objects.
       for (let phase in totals) {
-        ok(typeof(totals[phase]) == "number", `${phase} property should be a number`);
+        is(typeof(totals[phase]), "number", `${phase} property should be a number`);
       }
 
       for (let phase of phases) {
         ok(phase in totals, `${phase} is in totals`);
       }
     }
   }
 
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MinidumpAnalyzerUtils_h
+#define MinidumpAnalyzerUtils_h
+
+#ifdef XP_WIN
+#include <windows.h>
+#endif
+
+#include <vector>
+#include <map>
+#include <string>
+#include <algorithm>
+
+namespace CrashReporter {
+
+struct MinidumpAnalyzerOptions {
+  bool fullMinidump;
+  std::string forceUseModule;
+};
+
+extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
+
+#ifdef XP_WIN
+
+#if !defined(_MSC_VER)
+static inline std::string
+WideToMBCP(const std::wstring& wide, unsigned int cp, bool* success = nullptr)
+{
+  char* buffer = nullptr;
+  int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(),
+                                        -1, nullptr, 0, nullptr, nullptr);
+  if (buffer_size == 0) {
+    if (success) {
+      *success = false;
+    }
+
+    return "";
+  }
+
+  buffer = new char[buffer_size];
+  if (buffer == nullptr) {
+    if (success) {
+      *success = false;
+    }
+
+    return "";
+  }
+
+  WideCharToMultiByte(cp, 0, wide.c_str(),
+                      -1, buffer, buffer_size, nullptr, nullptr);
+  std::string mb = buffer;
+  delete [] buffer;
+
+  if (success) {
+    *success = true;
+  }
+
+  return mb;
+}
+#endif /* !defined(_MSC_VER) */
+
+static inline std::wstring
+UTF8ToWide(const std::string& aUtf8Str, bool *aSuccess = nullptr)
+{
+  wchar_t* buffer = nullptr;
+  int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
+                                        -1, nullptr, 0);
+  if (buffer_size == 0) {
+    if (aSuccess) {
+      *aSuccess = false;
+    }
+
+    return L"";
+  }
+
+  buffer = new wchar_t[buffer_size];
+
+  if (buffer == nullptr) {
+    if (aSuccess) {
+      *aSuccess = false;
+    }
+
+    return L"";
+  }
+
+  MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
+                      -1, buffer, buffer_size);
+  std::wstring str = buffer;
+  delete [] buffer;
+
+  if (aSuccess) {
+    *aSuccess = true;
+  }
+
+  return str;
+}
+
+static inline std::string
+WideToMBCS(const std::wstring &inp) {
+  int buffer_size = WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1,
+                                        nullptr, 0, NULL, NULL);
+  if (buffer_size == 0) {
+    return "";
+  }
+
+  std::vector<char> buffer(buffer_size);
+  buffer[0] = 0;
+
+  WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1,
+                      buffer.data(), buffer_size, NULL, NULL);
+
+  return buffer.data();
+}
+
+static inline std::string
+UTF8toMBCS(const std::string &inp) {
+  std::wstring wide = UTF8ToWide(inp);
+  std::string ret = WideToMBCS(wide);
+  return ret;
+}
+
+#endif // XP_WIN
+
+// Check if a file exists at the specified path
+
+static inline bool
+FileExists(const std::string& aPath)
+{
+#if defined(XP_WIN)
+  DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str());
+  return (attrs != INVALID_FILE_ATTRIBUTES);
+#else // Non-Windows
+  struct stat sb;
+  int ret = stat(aPath.c_str(), &sb);
+  if (ret == -1 || !(sb.st_mode & S_IFREG)) {
+    return false;
+  }
+
+  return true;
+#endif // XP_WIN
+}
+
+} // namespace
+
+#endif // MinidumpAnalyzerUtils_h
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+#include "MozStackFrameSymbolizer.h"
+
+#include "MinidumpAnalyzerUtils.h"
+
+#include "processor/cfi_frame_info.h"
+
+#include <iostream>
+#include <sstream>
+#include <fstream>
+
+namespace CrashReporter {
+
+  extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
+
+  using google_breakpad::CFIFrameInfo;
+
+MozStackFrameSymbolizer::MozStackFrameSymbolizer() :
+  StackFrameSymbolizer(nullptr, nullptr)
+{
+}
+
+MozStackFrameSymbolizer::SymbolizerResult
+MozStackFrameSymbolizer::FillSourceLineInfo(const CodeModules* modules,
+                                            const SystemInfo* system_info,
+                                            StackFrame* stack_frame)
+{
+  SymbolizerResult ret = StackFrameSymbolizer::FillSourceLineInfo(
+    modules, system_info, stack_frame);
+
+  if (ret == kNoError && this->HasImplementation() &&
+    stack_frame->function_name.empty()) {
+    // Breakpad's Stackwalker::InstructionAddressSeemsValid only considers an
+    // address valid if it has associated symbols.
+    //
+    // This makes sense for complete & accurate symbols, but ours may be
+    // incomplete or wrong. Returning a function name tells Breakpad we
+    // recognize this address as code, so it's OK to use in stack scanning.
+    // This function is only called with addresses that land in this module.
+    //
+    // This allows us to fall back to stack scanning in the case where we were
+    // unable to provide CFI.
+    stack_frame->function_name = "<unknown code>";
+  }
+  return ret;
+}
+
+CFIFrameInfo*
+MozStackFrameSymbolizer::FindCFIFrameInfo(const StackFrame* frame)
+{
+  std::string modulePath;
+
+  // For unit testing, support loading a specified module instead of
+  // the real one.
+  bool moduleHasBeenReplaced = false;
+  if (gMinidumpAnalyzerOptions.forceUseModule.size() > 0) {
+    modulePath = gMinidumpAnalyzerOptions.forceUseModule;
+    moduleHasBeenReplaced = true;
+  } else {
+    if (!frame->module) {
+      return nullptr;
+    }
+    modulePath = frame->module->code_file();
+  }
+
+  // Get/create the unwind parser.
+  auto itMod = mModuleMap.find(modulePath);
+  std::shared_ptr<ModuleUnwindParser> unwindParser;
+  if (itMod != mModuleMap.end()) {
+    unwindParser = itMod->second;
+  } else {
+    unwindParser.reset(new ModuleUnwindParser(modulePath));
+    mModuleMap[modulePath] = unwindParser;
+  }
+
+  UnwindCFI cfi;
+  DWORD offsetAddr;
+
+  if (moduleHasBeenReplaced) {
+    // If we are replacing a module, addresses will never line up.
+    // So just act like the 1st entry is correct.
+    offsetAddr = unwindParser->GetAnyOffsetAddr();
+  } else {
+    offsetAddr = frame->instruction - frame->module->base_address();
+  }
+
+  if (!unwindParser->GetCFI(offsetAddr, cfi)) {
+    return nullptr;
+  }
+
+  std::unique_ptr<CFIFrameInfo> rules(new CFIFrameInfo());
+
+  static const size_t exprSize = 50;
+  char expr[exprSize];
+  if (cfi.stackSize == 0) {
+    snprintf(expr, exprSize, "$rsp");
+  } else {
+    snprintf(expr, exprSize, "$rsp %d +", cfi.stackSize);
+  }
+  rules->SetCFARule(expr);
+
+  if (cfi.ripOffset == 0) {
+    snprintf(expr, exprSize, ".cfa ^");
+  } else {
+    snprintf(expr, exprSize, ".cfa %d - ^", cfi.ripOffset);
+  }
+  rules->SetRARule(expr);
+
+  return rules.release();
+}
+
+} // namespace CrashReporter
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MozStackFrameSymbolizer_h
+#define MozStackFrameSymbolizer_h
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+#include "Win64ModuleUnwindMetadata.h"
+
+#include "google_breakpad/processor/stack_frame_symbolizer.h"
+#include "google_breakpad/processor/stack_frame.h"
+
+#include <memory>
+
+namespace CrashReporter {
+
+using google_breakpad::CodeModule;
+using google_breakpad::CodeModules;
+using google_breakpad::SourceLineResolverInterface;
+using google_breakpad::StackFrame;
+using google_breakpad::StackFrameSymbolizer;
+using google_breakpad::SymbolSupplier;
+using google_breakpad::SystemInfo;
+
+class MozStackFrameSymbolizer : public StackFrameSymbolizer {
+  using google_breakpad::StackFrameSymbolizer::SymbolizerResult;
+
+  std::map<std::string, std::shared_ptr<ModuleUnwindParser>> mModuleMap;
+
+public:
+  MozStackFrameSymbolizer();
+
+  virtual SymbolizerResult FillSourceLineInfo(const CodeModules* modules,
+    const SystemInfo* system_info,
+    StackFrame* stack_frame);
+
+  virtual class google_breakpad::CFIFrameInfo* FindCFIFrameInfo(
+    const StackFrame* frame);
+};
+
+} // namespace CrashReporter
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
+
+#endif // MozStackFrameSymbolizer_h
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+#include "Win64ModuleUnwindMetadata.h"
+
+#include "MinidumpAnalyzerUtils.h"
+
+#include <windows.h>
+#include <winnt.h>
+#include <ImageHlp.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+namespace CrashReporter {
+
+union UnwindCode {
+  struct {
+    uint8_t offset_in_prolog;
+    uint8_t unwind_operation_code : 4;
+    uint8_t operation_info        : 4;
+  };
+  USHORT frame_offset;
+};
+
+enum UnwindOperationCodes {
+  UWOP_PUSH_NONVOL = 0,     // info == register number
+  UWOP_ALLOC_LARGE = 1,     // no info, alloc size in next 2 slots
+  UWOP_ALLOC_SMALL = 2,     // info == size of allocation / 8 - 1
+  UWOP_SET_FPREG = 3,       // no info, FP = RSP + UNWIND_INFO.FPRegOffset*16
+  UWOP_SAVE_NONVOL = 4,     // info == register number, offset in next slot
+  UWOP_SAVE_NONVOL_FAR = 5, // info == register number, offset in next 2 slots
+  UWOP_SAVE_XMM = 6,        // Version 1; undocumented
+  UWOP_EPILOG = 6,          // Version 2; undocumented
+  UWOP_SAVE_XMM_FAR = 7,    // Version 1; undocumented
+  UWOP_SPARE = 7,           // Version 2; undocumented
+  UWOP_SAVE_XMM128 = 8,     // info == XMM reg number, offset in next slot
+  UWOP_SAVE_XMM128_FAR = 9, // info == XMM reg number, offset in next 2 slots
+  UWOP_PUSH_MACHFRAME = 10  // info == 0: no error-code, 1: error-code
+};
+
+struct UnwindInfo {
+  uint8_t version       : 3;
+  uint8_t flags         : 5;
+  uint8_t size_of_prolog;
+  uint8_t count_of_codes;
+  uint8_t frame_register : 4;
+  uint8_t frame_offset   : 4;
+  UnwindCode unwind_code[1];
+};
+
+ModuleUnwindParser::~ModuleUnwindParser()
+{
+  if (mImg) {
+    ImageUnload(mImg);
+  }
+}
+
+void*
+ModuleUnwindParser::RvaToVa(ULONG aRva)
+{
+  return ImageRvaToVa(
+    mImg->FileHeader, mImg->MappedAddress, aRva, &mImg->LastRvaSection);
+}
+
+ModuleUnwindParser::ModuleUnwindParser(const std::string& aPath)
+  : mPath(aPath)
+{
+  // Convert wchar to native charset because ImageLoad only takes
+  // a PSTR as input.
+  std::string code_file = UTF8toMBCS(aPath);
+
+  mImg = ImageLoad((PSTR)code_file.c_str(), NULL);
+  if (!mImg || !mImg->FileHeader) {
+    return;
+  }
+
+  PIMAGE_OPTIONAL_HEADER64 optional_header = &mImg->FileHeader->OptionalHeader;
+  if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
+    return;
+  }
+
+  DWORD exception_rva = optional_header->
+    DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress;
+
+  DWORD exception_size = optional_header->
+    DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size;
+
+  auto funcs = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(exception_rva);
+  if (!funcs) {
+    return;
+  }
+
+  for (DWORD i = 0; i < exception_size / sizeof(*funcs); i++) {
+    mUnwindMap[funcs[i].BeginAddress] = &funcs[i];
+  }
+}
+
+bool
+ModuleUnwindParser::GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc,
+                                           UnwindCFI& aRet)
+{
+  DWORD unwind_rva = aFunc.UnwindInfoAddress;
+  // Holds RVA to all visited IMAGE_RUNTIME_FUNCTION_ENTRY, to avoid
+  // circular references.
+  std::set<DWORD> visited;
+
+  // Follow chained function entries
+  while (unwind_rva & 0x1) {
+    unwind_rva ^= 0x1;
+
+    if (visited.end() != visited.find(unwind_rva)) {
+      return false;
+    }
+    visited.insert(unwind_rva);
+
+    auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(unwind_rva);
+    if (!chained_func) {
+      return false;
+    }
+    unwind_rva = chained_func->UnwindInfoAddress;
+  }
+
+  visited.insert(unwind_rva);
+
+  auto unwind_info = (UnwindInfo*)RvaToVa(unwind_rva);
+  if (!unwind_info) {
+    return false;
+  }
+
+  DWORD stack_size = 8; // minimal stack size is 8 for RIP
+  DWORD rip_offset = 8;
+  do {
+    for (uint8_t c = 0; c < unwind_info->count_of_codes; c++) {
+      UnwindCode* unwind_code = &unwind_info->unwind_code[c];
+      switch (unwind_code->unwind_operation_code) {
+        case UWOP_PUSH_NONVOL: {
+          stack_size += 8;
+          break;
+        }
+        case UWOP_ALLOC_LARGE: {
+          if (unwind_code->operation_info == 0) {
+            c++;
+            if (c < unwind_info->count_of_codes) {
+              stack_size += (unwind_code + 1)->frame_offset * 8;
+            }
+          } else {
+            c += 2;
+            if (c < unwind_info->count_of_codes) {
+              stack_size += (unwind_code + 1)->frame_offset |
+                            ((unwind_code + 2)->frame_offset << 16);
+            }
+          }
+          break;
+        }
+        case UWOP_ALLOC_SMALL: {
+          stack_size += unwind_code->operation_info * 8 + 8;
+          break;
+        }
+        case UWOP_SET_FPREG:
+          // To correctly track RSP when it's been transferred to another
+          // register, we would need to emit CFI records for every unwind op.
+          // For simplicity, don't emit CFI records for this function as
+          // we know it will be incorrect after this point.
+          return false;
+        case UWOP_SAVE_NONVOL:
+        case UWOP_SAVE_XMM: // also v2 UWOP_EPILOG
+        case UWOP_SAVE_XMM128: {
+          c++; // skip slot with offset
+          break;
+        }
+        case UWOP_SAVE_NONVOL_FAR:
+        case UWOP_SAVE_XMM_FAR: // also v2 UWOP_SPARE
+        case UWOP_SAVE_XMM128_FAR: {
+          c += 2; // skip 2 slots with offset
+          break;
+        }
+        case UWOP_PUSH_MACHFRAME: {
+          if (unwind_code->operation_info) {
+            stack_size += 88;
+          } else {
+            stack_size += 80;
+          }
+          rip_offset += 80;
+          break;
+        }
+        default: {
+          return false;
+        }
+      }
+    }
+
+    if (unwind_info->flags & UNW_FLAG_CHAININFO) {
+      auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)(
+            (unwind_info->unwind_code +
+            ((unwind_info->count_of_codes + 1) & ~1)));
+
+      if (visited.end() != visited.find(chained_func->UnwindInfoAddress)) {
+        return false; // Circular reference
+      }
+
+      visited.insert(chained_func->UnwindInfoAddress);
+
+      unwind_info = (UnwindInfo*)RvaToVa(chained_func->UnwindInfoAddress);
+    } else {
+      unwind_info = nullptr;
+    }
+  } while (unwind_info);
+
+  aRet.beginAddress = aFunc.BeginAddress;
+  aRet.size = aFunc.EndAddress - aFunc.BeginAddress;
+  aRet.stackSize = stack_size;
+  aRet.ripOffset = rip_offset;
+  return true;
+}
+
+// For unit testing we sometimes need any address that's valid in this module.
+// Just return the first address we know of.
+DWORD
+ModuleUnwindParser::GetAnyOffsetAddr() const {
+  if (mUnwindMap.size() < 1) {
+    return 0;
+  }
+  return mUnwindMap.begin()->first;
+}
+
+bool
+ModuleUnwindParser::GetCFI(DWORD aAddress, UnwindCFI& aRet)
+{
+  // Figure out the begin address of the requested address.
+  auto itUW = mUnwindMap.lower_bound(aAddress + 1);
+  if (itUW == mUnwindMap.begin()) {
+   return false; // address before this module.
+  }
+  --itUW;
+
+  // Ensure that the function entry is big enough to contain this address.
+  IMAGE_RUNTIME_FUNCTION_ENTRY& func = *itUW->second;
+  if (aAddress > func.EndAddress) {
+    return false;
+  }
+
+  // Do we have CFI for this function already?
+  auto itCFI = mCFIMap.find(aAddress);
+  if (itCFI != mCFIMap.end()) {
+    aRet = itCFI->second;
+    return true;
+  }
+
+  // No, generate it.
+  if (!GenerateCFIForFunction(func, aRet)) {
+    return false;
+  }
+
+  mCFIMap[func.BeginAddress] = aRet;
+  return true;
+}
+
+} // namespace
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef Win64ModuleUnwindMetadata_h
+#define Win64ModuleUnwindMetadata_h
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+#include <functional>
+#include <map>
+#include <string>
+
+#include <windows.h>
+#include <winnt.h>
+#include <ImageHlp.h>
+
+namespace CrashReporter {
+
+struct UnwindCFI
+{
+  uint32_t beginAddress;
+  uint32_t size;
+  uint32_t stackSize;
+  uint32_t ripOffset;
+};
+
+// Does lazy-parsing of unwind info.
+class ModuleUnwindParser {
+  PLOADED_IMAGE mImg;
+  std::string mPath;
+
+  // Maps begin address to exception record.
+  // Populated upon construction.
+  std::map<DWORD, PIMAGE_RUNTIME_FUNCTION_ENTRY> mUnwindMap;
+
+  // Maps begin address to CFI.
+  // Populated as needed.
+  std::map<DWORD, UnwindCFI> mCFIMap;
+
+  bool GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc,
+                              UnwindCFI& aRet);
+  void* RvaToVa(ULONG aRva);
+
+public:
+  explicit ModuleUnwindParser(const std::string& aPath);
+  ~ModuleUnwindParser();
+  bool GetCFI(DWORD aAddress, UnwindCFI& aRet);
+  DWORD GetAnyOffsetAddr() const;
+};
+
+} // namespace
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
+
+#endif // Win64ModuleUnwindMetadata_h
--- a/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
+++ b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
@@ -27,22 +27,21 @@
 #elif defined(XP_UNIX) || defined(XP_MACOSX)
 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
 #endif
 
-// Path of the minidump to be analyzed.
-static string gMinidumpPath;
+#include "MinidumpAnalyzerUtils.h"
 
-// When set to true print out the full minidump analysis, otherwise only
-// include the crashing thread in the output.
-static bool gFullMinidump = false;
+#if XP_WIN && HAVE_64BIT_BUILD
+#include "MozStackFrameSymbolizer.h"
+#endif
 
 namespace CrashReporter {
 
 using std::ios;
 using std::ios_base;
 using std::hex;
 using std::ofstream;
 using std::map;
@@ -57,87 +56,20 @@ using google_breakpad::CodeModule;
 using google_breakpad::CodeModules;
 using google_breakpad::Minidump;
 using google_breakpad::MinidumpProcessor;
 using google_breakpad::PathnameStripper;
 using google_breakpad::ProcessResult;
 using google_breakpad::ProcessState;
 using google_breakpad::StackFrame;
 
-#ifdef XP_WIN
-
-#if !defined(_MSC_VER)
-static string WideToMBCP(const wstring& wide,
-                         unsigned int cp,
-                         bool* success = nullptr)
-{
-  char* buffer = nullptr;
-  int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(),
-                                        -1, nullptr, 0, nullptr, nullptr);
-  if(buffer_size == 0) {
-    if (success)
-      *success = false;
-    return "";
-  }
-
-  buffer = new char[buffer_size];
-  if(buffer == nullptr) {
-    if (success)
-      *success = false;
-    return "";
-  }
-
-  WideCharToMultiByte(cp, 0, wide.c_str(),
-                      -1, buffer, buffer_size, nullptr, nullptr);
-  string mb = buffer;
-  delete [] buffer;
-
-  if (success)
-    *success = true;
-
-  return mb;
-}
-#endif /* !defined(_MSC_VER) */
+MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
 
-static wstring UTF8ToWide(const string& aUtf8Str, bool *aSuccess = nullptr)
-{
-  wchar_t* buffer = nullptr;
-  int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
-                                        -1, nullptr, 0);
-  if (buffer_size == 0) {
-    if (aSuccess) {
-      *aSuccess = false;
-    }
-
-    return L"";
-  }
-
-  buffer = new wchar_t[buffer_size];
-
-  if (buffer == nullptr) {
-    if (aSuccess) {
-      *aSuccess = false;
-    }
-
-    return L"";
-  }
-
-  MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
-                      -1, buffer, buffer_size);
-  wstring str = buffer;
-  delete [] buffer;
-
-  if (aSuccess) {
-    *aSuccess = true;
-  }
-
-  return str;
-}
-
-#endif
+// Path of the minidump to be analyzed.
+static string gMinidumpPath;
 
 struct ModuleCompare {
   bool operator() (const CodeModule* aLhs, const CodeModule* aRhs) const {
     return aLhs->base_address() < aRhs->base_address();
   }
 };
 
 typedef map<const CodeModule*, unsigned int, ModuleCompare> OrderedModulesMap;
@@ -312,17 +244,18 @@ ConvertProcessStateToJSON(const ProcessS
   if (aProcessState.crashed()) {
     crashInfo["type"] = aProcessState.crash_reason();
     crashInfo["address"] = ToHex(aProcessState.crash_address());
 
     if (requestingThread != -1) {
       // Record the crashing thread index only if this is a full minidump
       // and all threads' stacks are present, otherwise only the crashing
       // thread stack is written out and this field is set to 0.
-      crashInfo["crashing_thread"] = gFullMinidump ? requestingThread : 0;
+      crashInfo["crashing_thread"] =
+        gMinidumpAnalyzerOptions.fullMinidump ? requestingThread : 0;
     }
   } else {
     crashInfo["type"] = Json::Value(Json::nullValue);
     // Add assertion info, if available
     string assertion = aProcessState.assertion();
 
     if (!assertion.empty()) {
       crashInfo["assertion"] = assertion;
@@ -340,17 +273,17 @@ ConvertProcessStateToJSON(const ProcessS
   }
 
   aRoot["modules"] = modules;
 
   // Threads
   Json::Value threads(Json::arrayValue);
   int threadCount = aProcessState.threads()->size();
 
-  if (!gFullMinidump && (requestingThread != -1)) {
+  if (!gMinidumpAnalyzerOptions.fullMinidump && (requestingThread != -1)) {
     // Only add the crashing thread
     Json::Value thread;
     Json::Value stack(Json::arrayValue);
     const CallStack* rawStack = aProcessState.threads()->at(requestingThread);
 
     ConvertStackToJSON(aProcessState, orderedModules, rawStack, stack);
     thread["frames"] = stack;
     threads.append(thread);
@@ -369,19 +302,24 @@ ConvertProcessStateToJSON(const ProcessS
   aRoot["threads"] = threads;
 }
 
 // Process the minidump file and append the JSON-formatted stack traces to
 // the node specified in |aRoot|
 
 static bool
 ProcessMinidump(Json::Value& aRoot, const string& aDumpFile) {
+#if XP_WIN && HAVE_64BIT_BUILD
+  MozStackFrameSymbolizer symbolizer;
+  MinidumpProcessor minidumpProcessor(&symbolizer, false);
+#else
   BasicSourceLineResolver resolver;
   // We don't have a valid symbol resolver so we pass nullptr instead.
   MinidumpProcessor minidumpProcessor(nullptr, &resolver);
+#endif
 
   // Process the minidump.
   Minidump dump(aDumpFile);
   if (!dump.Read()) {
     return false;
   }
 
   ProcessResult rv;
@@ -410,35 +348,16 @@ OpenAppend(const string& aFilename)
     new ofstream(WideToMBCP(UTF8ToWide(aFilename), CP_ACP).c_str(), mode);
 #endif  // _MSC_VER
 #else // Non-Windows
   ofstream* file = new ofstream(aFilename.c_str(), mode);
 #endif // XP_WIN
   return file;
 }
 
-// Check if a file exists at the specified path
-
-static bool
-FileExists(const string& aPath)
-{
-#if defined(XP_WIN)
-  DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str());
-  return (attrs != INVALID_FILE_ATTRIBUTES);
-#else // Non-Windows
-  struct stat sb;
-  int ret = stat(aPath.c_str(), &sb);
-  if (ret == -1 || !(sb.st_mode & S_IFREG)) {
-    return false;
-  }
-
-  return true;
-#endif // XP_WIN
-}
-
 // Update the extra data file by adding the StackTraces field holding the
 // JSON output of this program.
 
 static void
 UpdateExtraDataFile(const string &aDumpPath, const Json::Value& aRoot)
 {
   string extraDataPath(aDumpPath);
   int dot = extraDataPath.rfind('.');
@@ -468,17 +387,20 @@ using namespace CrashReporter;
 static void
 ParseArguments(int argc, char** argv) {
   if (argc <= 1) {
     exit(EXIT_FAILURE);
   }
 
   for (int i = 1; i < argc - 1; i++) {
     if (strcmp(argv[i], "--full") == 0) {
-      gFullMinidump = true;
+      gMinidumpAnalyzerOptions.fullMinidump = true;
+    } else if ((strcmp(argv[i], "--force-use-module") == 0) && (i < argc - 2)) {
+      gMinidumpAnalyzerOptions.forceUseModule = argv[i + 1];
+      ++i;
     } else {
       exit(EXIT_FAILURE);
     }
   }
 
   gMinidumpPath = argv[argc - 1];
 }
 
--- a/toolkit/crashreporter/minidump-analyzer/moz.build
+++ b/toolkit/crashreporter/minidump-analyzer/moz.build
@@ -22,13 +22,25 @@ if CONFIG['OS_TARGET'] != 'Android':
     LOCAL_INCLUDES += [
         '/toolkit/components/jsoncpp/include',
     ]
 
 
     if CONFIG['OS_TARGET'] == 'Darwin':
         DIST_SUBDIR = 'crashreporter.app/Contents/MacOS'
 
+if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'x86_64':
+    UNIFIED_SOURCES += [
+        'MozStackFrameSymbolizer.cpp',
+        'Win64ModuleUnwindMetadata.cpp',
+    ]
+
+    OS_LIBS += [
+        'Dbghelp',
+        'Imagehlp'
+    ]
+
+
 # Don't use the STL wrappers in the crashreporter clients; they don't
 # link with -lmozalloc, and it really doesn't matter here anyway.
 DisableStlWrapping()
 
 include('/toolkit/crashreporter/crashreporter.mozbuild')
--- a/toolkit/crashreporter/test/CrashTestUtils.jsm
+++ b/toolkit/crashreporter/test/CrashTestUtils.jsm
@@ -3,26 +3,39 @@
 
 this.EXPORTED_SYMBOLS = ["CrashTestUtils"];
 
 this.CrashTestUtils = {
   // These will be defined using ctypes APIs below.
   crash: null,
   dumpHasStream: null,
   dumpHasInstructionPointerMemory: null,
+  dumpWin64CFITestSymbols: null,
 
   // Constants for crash()
   // Keep these in sync with nsTestCrasher.cpp!
   CRASH_INVALID_POINTER_DEREF: 0,
   CRASH_PURE_VIRTUAL_CALL:     1,
   CRASH_RUNTIMEABORT:          2,
   CRASH_OOM:                   3,
   CRASH_MOZ_CRASH:             4,
   CRASH_ABORT:                 5,
   CRASH_UNCAUGHT_EXCEPTION:    6,
+  CRASH_X64CFI_NO_MANS_LAND:   7,
+  CRASH_X64CFI_LAUNCHER:       8,
+  CRASH_X64CFI_UNKNOWN_OPCODE: 9,
+  CRASH_X64CFI_PUSH_NONVOL:    10,
+  CRASH_X64CFI_ALLOC_SMALL:    11,
+  CRASH_X64CFI_ALLOC_LARGE:    12,
+  CRASH_X64CFI_SAVE_NONVOL:    15,
+  CRASH_X64CFI_SAVE_NONVOL_FAR: 16,
+  CRASH_X64CFI_SAVE_XMM128:    17,
+  CRASH_X64CFI_SAVE_XMM128_FAR: 18,
+  CRASH_X64CFI_EPILOG:         19,
+  CRASH_X64CFI_EOF:            20,
 
   // Constants for dumpHasStream()
   // From google_breakpad/common/minidump_format.h
   MD_THREAD_LIST_STREAM:       3,
   MD_MEMORY_INFO_LIST_STREAM:  16
 };
 
 // Grab APIs from the testcrasher shared library
@@ -58,8 +71,14 @@ CrashTestUtils.dumpHasInstructionPointer
               ctypes.default_abi,
               ctypes.bool,
               ctypes.char.ptr);
 
 CrashTestUtils.dumpCheckMemory = lib.declare("DumpCheckMemory",
                                              ctypes.default_abi,
                                              ctypes.bool,
                                              ctypes.char.ptr);
+
+CrashTestUtils.getWin64CFITestFnAddrOffset =
+  lib.declare("GetWin64CFITestFnAddrOffset",
+    ctypes.default_abi,
+    ctypes.int32_t,
+    ctypes.int16_t);
--- a/toolkit/crashreporter/test/moz.build
+++ b/toolkit/crashreporter/test/moz.build
@@ -19,16 +19,21 @@ UNIFIED_SOURCES += [
     'dumputils.cpp',
     'nsTestCrasher.cpp',
 ]
 
 SOURCES += [
   'ExceptionThrower.cpp',
 ]
 
+if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'x86_64':
+    SOURCES += [
+        'win64UnwindInfoTests.asm',
+    ]
+
 if CONFIG['CLANG_CL']:
     SOURCES['ExceptionThrower.cpp'].flags += [
         '-Xclang',
         '-fcxx-exceptions',
     ]
 elif not CONFIG['_MSC_VER']:
     SOURCES['ExceptionThrower.cpp'].flags += [
         '-fexceptions',
--- a/toolkit/crashreporter/test/nsTestCrasher.cpp
+++ b/toolkit/crashreporter/test/nsTestCrasher.cpp
@@ -2,16 +2,17 @@
 
 #include <stdio.h>
 
 #include "nscore.h"
 #include "mozilla/Unused.h"
 #include "ExceptionThrower.h"
 
 #ifdef XP_WIN
+#include <malloc.h>
 #include <windows.h>
 #endif
 
 /*
  * This pure virtual call example is from MSDN
  */
 class A;
 
@@ -38,23 +39,90 @@ void fcn( A* p )
 
 void PureVirtualCall()
 {
   // generates a pure virtual function call
   B b;
   b.use(); // make sure b's actually used
 }
 
+extern "C" {
+#if XP_WIN && HAVE_64BIT_BUILD
+  // Implementation in win64unwindInfoTests.asm
+  uint64_t x64CrashCFITest_NO_MANS_LAND(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_Launcher(uint64_t returnpfn, void* testProc);
+  uint64_t x64CrashCFITest_UnknownOpcode(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_PUSH_NONVOL(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_ALLOC_SMALL(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_ALLOC_LARGE(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_SAVE_NONVOL(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_SAVE_NONVOL_FAR(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_SAVE_XMM128(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_SAVE_XMM128_FAR(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_EPILOG(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_EOF(uint64_t returnpfn, void*);
+#endif // XP_WIN && HAVE_64BIT_BUILD
+}
+
 // Keep these in sync with CrashTestUtils.jsm!
 const int16_t CRASH_INVALID_POINTER_DEREF = 0;
 const int16_t CRASH_PURE_VIRTUAL_CALL     = 1;
 const int16_t CRASH_OOM                   = 3;
 const int16_t CRASH_MOZ_CRASH             = 4;
 const int16_t CRASH_ABORT                 = 5;
 const int16_t CRASH_UNCAUGHT_EXCEPTION    = 6;
+const int16_t CRASH_X64CFI_NO_MANS_LAND   = 7;
+const int16_t CRASH_X64CFI_LAUNCHER       = 8;
+const int16_t CRASH_X64CFI_UNKNOWN_OPCODE = 9;
+const int16_t CRASH_X64CFI_PUSH_NONVOL    = 10;
+const int16_t CRASH_X64CFI_ALLOC_SMALL    = 11;
+const int16_t CRASH_X64CFI_ALLOC_LARGE    = 12;
+const int16_t CRASH_X64CFI_SAVE_NONVOL    = 15;
+const int16_t CRASH_X64CFI_SAVE_NONVOL_FAR = 16;
+const int16_t CRASH_X64CFI_SAVE_XMM128    = 17;
+const int16_t CRASH_X64CFI_SAVE_XMM128_FAR = 18;
+const int16_t CRASH_X64CFI_EPILOG         = 19;
+const int16_t CRASH_X64CFI_EOF            = 20;
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+typedef decltype(&x64CrashCFITest_UnknownOpcode) win64CFITestFnPtr_t;
+
+static std::map<int16_t, win64CFITestFnPtr_t>
+GetWin64CFITestMap() {
+  std::map<int16_t, win64CFITestFnPtr_t> ret = {
+    { CRASH_X64CFI_NO_MANS_LAND, x64CrashCFITest_NO_MANS_LAND},
+    { CRASH_X64CFI_LAUNCHER, x64CrashCFITest_Launcher},
+    { CRASH_X64CFI_UNKNOWN_OPCODE, x64CrashCFITest_UnknownOpcode},
+    { CRASH_X64CFI_PUSH_NONVOL, x64CrashCFITest_PUSH_NONVOL},
+    { CRASH_X64CFI_ALLOC_SMALL, x64CrashCFITest_ALLOC_SMALL },
+    { CRASH_X64CFI_ALLOC_LARGE, x64CrashCFITest_ALLOC_LARGE },
+    { CRASH_X64CFI_SAVE_NONVOL, x64CrashCFITest_SAVE_NONVOL },
+    { CRASH_X64CFI_SAVE_NONVOL_FAR, x64CrashCFITest_SAVE_NONVOL_FAR },
+    { CRASH_X64CFI_SAVE_XMM128, x64CrashCFITest_SAVE_XMM128 },
+    { CRASH_X64CFI_SAVE_XMM128_FAR, x64CrashCFITest_SAVE_XMM128_FAR },
+    { CRASH_X64CFI_EPILOG, x64CrashCFITest_EPILOG },
+    { CRASH_X64CFI_EOF, x64CrashCFITest_EOF }
+  };
+  // ret values point to jump table entries, not the actual function bodies.
+  // Get the correct pointer by calling the function with returnpfn=1
+  for (auto it = ret.begin(); it != ret.end(); ++ it) {
+    it->second = (win64CFITestFnPtr_t)it->second(1, nullptr);
+  }
+  return ret;
+}
+
+void ReserveStack() {
+  // This ensures our tests have enough reserved stack space.
+  uint8_t* p = (uint8_t*)alloca(1024000);
+  // This ensures we don't optimized away this meaningless code at build time.
+  mozilla::Unused << (int)(uint64_t)p;
+}
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
 
 extern "C" NS_EXPORT
 void Crash(int16_t how)
 {
   switch (how) {
   case CRASH_INVALID_POINTER_DEREF: {
     volatile int* foo = (int*)0x42;
     *foo = 0;
@@ -79,16 +147,37 @@ void Crash(int16_t how)
   case CRASH_ABORT: {
     abort();
     break;
   }
   case CRASH_UNCAUGHT_EXCEPTION: {
     ThrowException();
     break;
   }
+#if XP_WIN && HAVE_64BIT_BUILD
+  case CRASH_X64CFI_UNKNOWN_OPCODE:
+  case CRASH_X64CFI_PUSH_NONVOL:
+  case CRASH_X64CFI_ALLOC_SMALL:
+  case CRASH_X64CFI_ALLOC_LARGE:
+  case CRASH_X64CFI_SAVE_NONVOL:
+  case CRASH_X64CFI_SAVE_NONVOL_FAR:
+  case CRASH_X64CFI_SAVE_XMM128:
+  case CRASH_X64CFI_SAVE_XMM128_FAR:
+  case CRASH_X64CFI_EPILOG: {
+    ReserveStack();
+    auto m = GetWin64CFITestMap();
+    if (m.find(how) == m.end()) {
+      break;
+    }
+    auto pfnTest = m[how];
+    auto pfnLauncher = m[CRASH_X64CFI_LAUNCHER];
+    pfnLauncher(0, pfnTest);
+    break;
+  }
+#endif // XP_WIN && HAVE_64BIT_BUILD
   default:
     break;
   }
 }
 
 char testData[32];
 
 extern "C" NS_EXPORT
@@ -114,8 +203,25 @@ static LONG WINAPI HandleException(EXCEP
 }
 
 extern "C" NS_EXPORT
 void TryOverrideExceptionHandler()
 {
   SetUnhandledExceptionFilter(HandleException);
 }
 #endif
+
+extern "C" NS_EXPORT uint32_t
+GetWin64CFITestFnAddrOffset(int16_t fnid) {
+#if XP_WIN && HAVE_64BIT_BUILD
+  // fnid uses the same constants as Crash().
+  // Returns the RVA of the requested function.
+  // Returns 0 on failure.
+  auto m = GetWin64CFITestMap();
+  if (m.find(fnid) == m.end()) {
+    return 0;
+  }
+  uint64_t moduleBase = (uint64_t)GetModuleHandleW(L"testcrasher.dll");
+  return ((uint64_t)m[fnid]) - moduleBase;
+#else
+  return 0;
+#endif // XP_WIN && HAVE_64BIT_BUILD
+}
--- a/toolkit/crashreporter/test/unit/head_crashreporter.js
+++ b/toolkit/crashreporter/test/unit/head_crashreporter.js
@@ -1,13 +1,14 @@
-var {utils: Cu} = Components;
+var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
 
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://testing-common/AppData.jsm", this);
+Cu.import("resource://gre/modules/AppConstants.jsm");
 
 function getEventDir() {
   return OS.Path.join(do_get_tempdir().path, "crash-events");
 }
 
 /*
  * Run an xpcshell subprocess and crash it.
  *
@@ -19,25 +20,27 @@ function getEventDir() {
  *        This code will be evaluted between crasher_subprocess_head.js
  *        and crasher_subprocess_tail.js, so it will have access
  *        to everything defined in crasher_subprocess_head.js,
  *        which includes "crashReporter", a variable holding
  *        the crash reporter service.
  *
  * @param callback
  *        A JavaScript function to be called after the subprocess
- *        crashes. It will be passed (minidump, extra), where
- *         minidump is an nsIFile of the minidump file produced,
- *         and extra is an object containing the key,value pairs from
- *         the .extra file.
+ *        crashes. It will be passed (minidump, extra, extrafile), where
+ *         - minidump is an nsIFile of the minidump file produced,
+ *         - extra is an object containing the key,value pairs from
+ *           the .extra file.
+ *         - extrafile is an nsIFile of the extra file
  *
  * @param canReturnZero
  *       If true, the subprocess may return with a zero exit code.
  *       Certain types of crashes may not cause the process to
  *       exit with an error.
+ *
  */
 function do_crash(setup, callback, canReturnZero) {
   // get current process filename (xpcshell)
   let ds = Components.classes["@mozilla.org/file/directory_service;1"]
     .getService(Components.interfaces.nsIProperties);
   let bin = ds.get("XREExeF", Components.interfaces.nsIFile);
   if (!bin.exists()) {
     // weird, can't find xpcshell binary?
@@ -95,16 +98,42 @@ function getMinidump() {
     if (f.leafName.substr(-4) == ".dmp") {
       return f;
     }
   }
 
   return null;
 }
 
+function runMinidumpAnalyzer(dumpFile, additionalArgs) {
+  if (AppConstants.platform !== "win") {
+    return;
+  }
+
+  // find minidump-analyzer executable.
+  let ds = Cc["@mozilla.org/file/directory_service;1"]
+             .getService(Ci.nsIProperties);
+  let bin = ds.get("XREExeF", Ci.nsIFile);
+  ok(bin && bin.exists());
+  bin = bin.parent;
+  ok(bin && bin.exists());
+  bin.append("minidump-analyzer.exe");
+  ok(bin.exists());
+
+  let process = Cc["@mozilla.org/process/util;1"]
+                  .createInstance(Ci.nsIProcess);
+  process.init(bin);
+  let args = [];
+  if (additionalArgs) {
+    args = args.concat(additionalArgs);
+  }
+  args.push(dumpFile.path);
+  process.run(true /* blocking */, args, args.length);
+}
+
 function handleMinidump(callback) {
   // find minidump
   let minidump = getMinidump();
 
   if (minidump == null) {
     do_throw("No minidump found!");
   }
 
@@ -126,17 +155,17 @@ function handleMinidump(callback) {
       memoryfile.remove(false);
     }
   });
 
   do_check_true(extrafile.exists());
   let extra = parseKeyValuePairsFromFile(extrafile);
 
   if (callback) {
-    callback(minidump, extra);
+    callback(minidump, extra, extrafile);
   }
 
   if (minidump.exists()) {
     minidump.remove(false);
   }
   if (extrafile.exists()) {
     extrafile.remove(false);
   }
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/head_win64cfi.js
@@ -0,0 +1,193 @@
+/* import-globals-from head_crashreporter.js */
+
+let gTestCrasherSyms = null;
+let gModules = null;
+
+// Returns the offset (int) of an IP with a given base address.
+// This is effectively (ip - base), except a bit more complication due to
+// Javascript's shaky handling of 64-bit integers.
+// base & ip are passed as hex strings.
+function getModuleOffset(base, ip) {
+  let i = 0;
+  // Find where the two addresses diverge, which enables us to perform a 32-bit
+  // subtraction.
+  // e.g.    "0x1111111111112222"
+  //       - "0x1111111111111111"
+  // becomes 2222 - 1111
+  for (; i < base.length; ++i) {
+    if (base[i] != ip[i]) {
+      break;
+    }
+  }
+  if (i == base.length) {
+    return 0;
+  }
+  let lhs2 = "0x" + base.substring(i);
+  let rhs2 = "0x" + ip.substring(i);
+  return parseInt(rhs2) - parseInt(lhs2);
+}
+
+// Uses gTestCrasherSyms to convert an address to a symbol.
+function findNearestTestCrasherSymbol(addr) {
+  addr += 1; // Breakpad sometimes offsets addresses; correct for this.
+  let closestDistance = null;
+  let closestSym = null;
+  for (let sym in gTestCrasherSyms) {
+    if (addr >= gTestCrasherSyms[sym]) {
+      let thisDistance = addr - gTestCrasherSyms[sym];
+      if (closestDistance === null || thisDistance < closestDistance) {
+        closestDistance = thisDistance;
+        closestSym = sym;
+      }
+    }
+  }
+  if (closestSym === null) {
+    return null;
+  }
+  return { symbol: closestSym, offset: closestDistance }
+}
+
+// Populate known symbols for testcrasher.dll.
+// Use the same prop names as from CrashTestUtils to avoid the need for mapping.
+function initTestCrasherSymbols() {
+  gTestCrasherSyms = { };
+  for (let k in CrashTestUtils) {
+    // Not all keys here are valid symbol names. getWin64CFITestFnAddrOffset
+    // will return 0 in those cases, no need to filter here.
+    if (Number.isInteger(CrashTestUtils[k])) {
+      let t = CrashTestUtils.getWin64CFITestFnAddrOffset(CrashTestUtils[k]);
+      if (t > 0) {
+        gTestCrasherSyms[k] = t;
+      }
+    }
+  }
+}
+
+function stackFrameToString(frameIndex, frame) {
+  // Calculate the module offset.
+  let ip = frame.ip;
+  let symbol = "";
+  let moduleOffset = "unknown_offset";
+  let filename = "unknown_module";
+
+  if (typeof frame.module_index !== "undefined" && (frame.module_index >= 0)
+    && (frame.module_index < gModules.length)) {
+
+    let base = gModules[frame.module_index].base_addr;
+    moduleOffset = getModuleOffset(base, ip);
+    filename = gModules[frame.module_index].filename;
+
+    if (filename === "testcrasher.dll") {
+      let nearestSym = findNearestTestCrasherSymbol(moduleOffset);
+      if (nearestSym !== null) {
+        symbol = nearestSym.symbol + "+" + nearestSym.offset.toString(16);
+      }
+    }
+  }
+
+  let ret = "frames[" + frameIndex + "] ip=" + ip +
+    " " + symbol +
+    ", module:" + filename +
+    ", trust:" + frame.trust +
+    ", moduleOffset:" + moduleOffset.toString(16);
+  return ret;
+}
+
+function dumpStackFrames(frames, maxFrames) {
+  for (let i = 0; i < Math.min(maxFrames, frames.length); ++i) {
+    do_print(stackFrameToString(i, frames[i]));
+  }
+}
+
+// Test that the top of the given stack (from extra data) matches the given
+// expected frames.
+//
+// expected is { symbol: "", trust: "" }
+function assertStack(stack, expected) {
+  for (let i = 0; i < stack.length; ++i) {
+    if (i >= expected.length) {
+      ok("Top stack frames were expected");
+      return;
+    }
+    let frame = stack[i];
+    let expectedFrame = expected[i];
+    let dumpThisFrame = function() {
+      do_print("  Actual frame: " + stackFrameToString(i, frame));
+      do_print("Expected { symbol: " + expectedFrame.symbol + ", trust: " + expectedFrame.trust + "}");
+    };
+
+    if (expectedFrame.trust) {
+      if (frame.trust !== expectedFrame.trust) {
+        dumpThisFrame();
+        do_print("Expected frame trust did not match.");
+        ok(false);
+      }
+    }
+
+    if (expectedFrame.symbol) {
+      if (typeof frame.module_index === "undefined") {
+        // Without a module_index, it happened in an unknown module. Currently
+        // you can't specify an expected "unknown" module.
+        do_print("Unknown symbol in unknown module.");
+        ok(false);
+      }
+      if (frame.module_index < 0 || frame.module_index >= gModules.length) {
+        dumpThisFrame();
+        do_print("Unknown module.");
+        ok(false);
+        return;
+      }
+      let base = gModules[frame.module_index].base_addr;
+      let moduleOffset = getModuleOffset(base, frame.ip);
+      let filename = gModules[frame.module_index].filename;
+      if (filename == "testcrasher.dll") {
+        let nearestSym = findNearestTestCrasherSymbol(moduleOffset);
+        if (nearestSym === null) {
+          dumpThisFrame();
+          do_print("Unknown symbol.");
+          ok(false);
+          return;
+        }
+
+        if (nearestSym.symbol !== expectedFrame.symbol) {
+          dumpThisFrame();
+          do_print("Mismatching symbol.");
+          ok(false);
+        }
+      }
+    }
+  }
+}
+
+// Performs a crash, runs minidump-analyzer, and checks expected stack analysis.
+//
+// how: The crash to perform. Constants defined in both CrashTestUtils.jsm
+//   and nsTestCrasher.cpp (i.e. CRASH_X64CFI_PUSH_NONVOL)
+// expectedStack: An array of {"symbol", "trust"} where trust is "cfi",
+//   "context", "scan", et al. May be null if you don't need to check the stack.
+// minidumpAnalyzerArgs: An array of additional arguments to pass to
+//   minidump-analyzer.exe.
+function do_x64CFITest(how, expectedStack, minidumpAnalyzerArgs) {
+
+  // Setup is run in the subprocess so we cannot use any closures.
+  let setupFn = "crashType = CrashTestUtils." + how + ";";
+
+  let callbackFn = function(minidumpFile, extra, extraFile) {
+    runMinidumpAnalyzer(minidumpFile, minidumpAnalyzerArgs);
+
+    // Refresh updated extra data
+    extra = parseKeyValuePairsFromFile(extraFile);
+
+    initTestCrasherSymbols();
+    let stackTraces = JSON.parse(extra.StackTraces);
+    let crashingThreadIndex = stackTraces.crash_info.crashing_thread;
+    gModules = stackTraces.modules;
+    let crashingFrames = stackTraces.threads[crashingThreadIndex].frames;
+
+    dumpStackFrames(crashingFrames, 10);
+
+    assertStack(crashingFrames, expectedStack);
+  };
+
+  do_crash(setupFn, callbackFn, true, true);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_ALLOC_LARGE", [
+      { symbol: "CRASH_X64CFI_ALLOC_LARGE", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL", [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_EPILOG", [
+      { symbol: "CRASH_X64CFI_EPILOG", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5283cc5df7e0a7abb20dd540cc203c4266702fff
GIT binary patch
literal 1440
zc$|e)-%C?b9RJ>J*K9U7hQ@m8E>1tF7@^aLo)*o#()__DM0UwdH*~Sxjd?8<9~#R*
zFM*Z58SGD}NRgfdjVL#Z48*LLelgI89(xFG=iGCz+pbVw_T_uNzrW{uK6jmcvtR}Q
z3uZ+DSO!Wy_&=>+u0Q^=9$p+Sx|f+i(cL>7AK}uO)KDgp<OU<6n3A}G7?&9pxwy#r
zyF*+u6^(gpHY?I<Y}GBketc_ZPUo(9$K0%X{xtUpON;lz{6kf4&l5cu9~>re`V2j7
z0MYwq_)-X7*KNPx1k{=8!NFj^iP}D6>7!>KHiJXs8ZAecd3HYz?<Jh2QLrx#>QzT@
z;rufddK&8p4pJB|J^k<XR#A_+x{r}OQfy4Z($3KN9ZdNiI&rwIClifG5j>}f*5ou<
zNk07Zq-h+|?r=eq#yEajK(+j|gdErS>>h`^&TQX%Bh}{6Nj1j4G>JI-l7#wq56!HX
zii)CKS|$%5Z^;`<V-cTnzZL0w;K2eS*&Xr?M~6J(>X5(nuNDdgwf;3ew->fA3CK;#
z-P~nMKgxAB<#$Tslvd{_FAFHm=hnhFTg%Pv<<&t1GRPzzge`qFq+ij~pE0Gg*jLqS
z+z@<B4{l!J<puN<t9*8i^ypuO9>b0Z>DV;UoxyuE<x1ZX%ng_w7Rr@A8|F34$U?c&
zS8wPQ4EaMFcR#+ht}nJvo$Gp)ZwaY*_g@r}<ARtP74O8ws4&**6@sxuEHV<?H{vHB
z0DaC%YQNkYAXp;UQgMcoBC+e3^u!5PPz$l?xYlt-?&)H59ZgJ3L<fejzht49@{DPC
zWa<62T1}+Z`D$CUiaEBbr&pZ=+gxQ^D?s_X({b!}vfzxK2B-HFI2i<Wcu2--QRlEe
zuSqjD7Hr<5U}GAfCQbY3)W;tDMsza1_olgXG~tim2DsW5>}m@%H+iCoMA=XF_{2o-
UpT}|s^B)WSf9l3PY7O=N0y)qbQvd(}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js
@@ -0,0 +1,17 @@
+
+function run_test() {
+  // Test that minidump-analyzer gracefully handles chained
+  // unwind code entries that form a circular reference
+  // (infinite loop).
+  let exe = do_get_file("test_crash_win64cfi_infinite_code_chain.exe");
+  ok(exe);
+
+  // Perform a crash. We won't get unwind info, but make sure the stack scan
+  // still works.
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
+    [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ],
+    ["--force-use-module", exe.path]);
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..de48576f6536cc6895f23297752690da1a694a10
GIT binary patch
literal 1440
zc$|e)-Ahwp7=O+_)MncbLt|ZajJFRWM(FaQt3{hlnjcI-s?j#x(8a!*M^h7GtpYs+
zRvKLx_zwsbCDN6k5T&cg3o*O04+eVCWf#HedCxho`w;43Kc4q}zTfx#owZ$=0zCj2
z5V8!g2&%Z?|FVRz9C>4bR|oP9i<~dt&^-_v6w;Yge<qv|`ohU%N)&pdLS`r_#FB!$
zqf1DnB2kCgY$95XjoR05A79^^W@w+`r>B(lm+42S4bIOq4;8&RL-+nz-vEtcGjw_Y
zBKP$WeDA-^Y`@?r)aoq2bLiKpwy&sN>e_{7;5AffIZV$P_u}wgLRmEm_SF7;*%IuS
zf3jqs#yx@k62ePQ|NDB&q*Gtsr&1hZbVNjL<<$9ku6Pb60=9KzB4II%bL!NZf~HHw
zg+E7HjU(C_&S<(a&N~qxHQou4@IBt_u7F)9H}Ad^YjWh65@TH$CxUfBB*C5P$<;z$
zmgVz{^Z}#|>4RLK$5rmPEPnSrm?bp3Rl3TzN`v-R>05AlZEa1d-{Z~ghO7$#(m=~y
z+c6e?6zi-@ALaUcTAiO>DL~TR+)4<uwcPCPoH9s&4l<5|7z^7K=`Ae%1y?wQzOr7U
z6~TvDaMO}knkCP1>&>pv9>Hbkthgg8jGH?3&ft??MXB!~q7K0uRFwM6h!q4es3`SW
zDth_5++CjA?(JK{Ph0ModRFJ3%=45t?sI{}XdsyyO5ThmBY}}-XTTqgN5g~BJtIDO
zFR(dF<>G~IALSC|mclcVNK!lFq$5VTLe5ef<2B<MJ*S;xdongQ7U}Irf5D(~$}ytx
z(WstZtJO$roi4XEDV#Hw^>iw8pv_*iH3JmCJH}(qRt>hu39vbjgN-9li$iiI1DKo^
zFuklwb7mu$orl28)j?HS?Zf1XasM~Mr1HJj^=(6OcWfBolE>ff@ijF%BJp_9Pw(*=
W;s)l;q25CLF{uAf9oVC$qTXLgycI0~
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js
@@ -0,0 +1,17 @@
+
+function run_test() {
+  // Test that minidump-analyzer gracefully handles chained
+  // IMAGE_RUNTIME_FUNCTION_ENTRY items that form a circular reference
+  // (infinite loop).
+  let exe = do_get_file("test_crash_win64cfi_infinite_entry_chain.exe");
+  ok(exe);
+
+  // Perform a crash. We won't get unwind info, but make sure the stack scan
+  // still works.
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
+    [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ],
+    ["--force-use-module", exe.path]);
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ab4ce326bdf34b9e417107d2d22adb3d01b006ee
GIT binary patch
literal 1440
zc$|e)-Ahwp7=O+_)Moo&XsoM_@%BN*2%TPZwP@y%<_DV~*&#RG(8YE(=CM?~Xe<Lg
z1Xg-yus@+9MY<9+qI4D+h#6h<#Xv8*>>^n2d*1W9?FjX-hv$91pZ9rxXB~aBU<3dY
z=3)_G87O(-|FnYHcH*TCUL46cEOY*RL(gz*giocDL+NmW9}G)UQsxJue0o&kV-oM{
z3i63$B<i+WEl8`eUAy@D@vYrC#+~zyxmor6Y3>o0CeMfYhpOC}Cwd?@I85T$4Bf2&
zk^4q)d<kA>wqI}(Y7I7UaM*94w$E64>Di0T;Lx~A%V9ESK8VA631w&$9Eihu#SvUM
z|7@9F#Dw55h49i-|6XqebsH=D=*cZd$7C$+9G&06mF~fa$8Fu|NLUWzISsTXugP-q
z;-5Q3<H+nOe5Z?=)W-?aBB~LlW#s4;GW$I4I<s@{ja-vOr_>nx(j?;TOET);tDade
z<ncN#FOvsQww29deIB2(zl-vB|APfYvfGs#j&^0l)vkQ&U)|W)Q0w;!+5M1xNkk2#
z+|506;YX>?mh!GxKc&_ADJUXJ3E8y}&en1>`#E(Gkqk142Qe4E>e8>W^k-b*9QIZ8
z8rKCMXTeP?g0g^~VpYhjkskf4(5>4MA%;x@-5I<$QZDx$#au^p6XkNB74sTqWTIT|
zv*~&Tg1+G9-H&fA>x*qu=R05Jnu96~e3!(;xF{t@r8_YxB966q#6UD24Ua?*jQGh1
zfX!J>ZC85yqzJ*5iZhguh@D~59V1vl&BVrV&2UEU>Ef7<CMG5#14GzfFi}jo$22^e
z>HW1@jilCvN?VhPIder%k2(joxk|Pc(6CCd%W%v&tHBvL15VFraB>K0@sOOw1Qw4C
zEYGV_oYf3g&oQuabx@U}eHitb4}T+!^zXf8>==#vVz&XVwFWv{{Y{PTNIYKhlRZ8$
V(EI1G+{OIIME{?<aE}^Yy}vLX7<&K!
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js
@@ -0,0 +1,16 @@
+
+function run_test() {
+  // Test that minidump-analyzer gracefully handles an invalid pointer to the
+  // exception unwind information.
+  let exe = do_get_file("test_crash_win64cfi_invalid_exception_rva.exe");
+  ok(exe);
+
+  // Perform a crash. We won't get unwind info, but make sure the stack scan
+  // still works.
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
+    [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ],
+    ["--force-use-module", exe.path]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.exe
@@ -0,0 +1,1 @@
+this is not a valid PE file.
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js
@@ -0,0 +1,15 @@
+
+function run_test() {
+  // Test that minidump-analyzer gracefully handles corrupt PE files.
+  let exe = do_get_file("test_crash_win64cfi_not_a_pe.exe");
+  ok(exe);
+
+  // Perform a crash. We won't get unwind info, but make sure the stack scan
+  // still works.
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
+    [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ],
+    ["--force-use-module", exe.path]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_PUSH_NONVOL", [
+      { symbol: "CRASH_X64CFI_PUSH_NONVOL", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL", [
+      { symbol: "CRASH_X64CFI_SAVE_NONVOL", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL_FAR", [
+      { symbol: "CRASH_X64CFI_SAVE_NONVOL_FAR", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_XMM128", [
+      { symbol: "CRASH_X64CFI_SAVE_XMM128", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_XMM128_FAR", [
+      { symbol: "CRASH_X64CFI_SAVE_XMM128_FAR", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js
@@ -0,0 +1,13 @@
+
+function run_test() {
+  // In the case of an unknown unwind code or missing CFI,
+  // make certain we can still walk the stack via stack scan. The crashing
+  // function places NO_MANS_LAND on the stack so it will get picked up via
+  // stack scan.
+  do_x64CFITest("CRASH_X64CFI_UNKNOWN_OPCODE", [
+      { symbol: "CRASH_X64CFI_UNKNOWN_OPCODE", trust: "context" },
+      // Trust may either be scan or frame_pointer; we don't really care as
+      // long as the address is expected.
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ]);
+}
--- a/toolkit/crashreporter/test/unit/xpcshell.ini
+++ b/toolkit/crashreporter/test/unit/xpcshell.ini
@@ -32,8 +32,70 @@ skip-if = os != 'win'
 
 [test_crashreporter_appmem.js]
 # we need to skip this due to bug 838613
 skip-if = (os != 'win' && os != 'linux') || (os=='linux' && bits==32)
 
 [test_crash_AsyncShutdown.js]
 [test_event_files.js]
 [test_crash_terminator.js]
+
+[test_crash_win64cfi_unknown_op.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_push_nonvol.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_alloc_small.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_alloc_large.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_save_nonvol.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_save_nonvol_far.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_save_xmm128.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_save_xmm128_far.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_epilog.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_infinite_entry_chain.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+support-files = test_crash_win64cfi_infinite_entry_chain.exe
+
+[test_crash_win64cfi_infinite_code_chain.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+support-files = test_crash_win64cfi_infinite_code_chain.exe
+
+[test_crash_win64cfi_invalid_exception_rva.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+support-files = test_crash_win64cfi_invalid_exception_rva.exe
+
+[test_crash_win64cfi_not_a_pe.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+support-files = test_crash_win64cfi_not_a_pe.exe
+
+
+
+
+
+
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/win64UnwindInfoTests.asm
@@ -0,0 +1,382 @@
+; 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/.
+
+; Comments indicate stack memory layout during execution.
+; For example at the top of a function, where RIP just points to the return
+; address, the stack looks like
+; rip = [ra]
+; And after pushing rax to the stack,
+; rip = [rax][ra]
+; And then, after allocating 20h bytes on the stack,
+; rip = [..20..][rax][ra]
+; And then, after pushing a function pointer,
+; rip = [pfn][..20..][rax][ra]
+
+include ksamd64.inc
+
+.code
+
+; It helps to add padding between functions so they're not right up against
+; each other. Adds clarity to debugging, and gives a bit of leeway when
+; searching for symbols (e.g. a function whose last instruction is CALL
+; would push a return address that's in the next function.)
+PaddingBetweenFunctions macro
+  repeat 10h
+     int 3
+  endm
+endm
+
+DoCrash macro
+  mov rax, 7
+  mov byte ptr [rax], 9
+endm
+
+PaddingBetweenFunctions
+
+; There is no rip addressing mode in x64. The only way to get the value
+; of rip is to call a function, and pop it from the stack.
+WhoCalledMe proc
+  pop rax ; rax is now ra
+  push rax ; Restore ra so this function can return.
+  sub rax, 5 ; Correct for the size of the call instruction
+  ret
+WhoCalledMe endp
+
+PaddingBetweenFunctions
+
+; Any function that we expect to test against on the stack, we'll need its
+; real address. If we use function pointers in C, we'll get the address to jump
+; table entries. This bit of code at the beginning of each function will
+; return the real address we'd expect to see in stack traces.
+;
+; rcx (1st arg) = mode
+; rax (return)  = address of either NO_MANS_LAND or this function.
+;
+; When mode is 0, we place the address of NO_MANS_LAND in RAX, for the function
+; to use as it wants. This is just for convenience because almost all functions
+; here need this address at some point.
+;
+; When mode is 1, the address of this function is returned.
+TestHeader macro
+  call WhoCalledMe
+  test rcx, rcx
+  je continue_test
+  ret
+continue_test:
+  inc rcx
+  call x64CrashCFITest_NO_MANS_LAND
+  xor rcx, rcx
+endm
+
+; The point of this is to add a stack frame to test against.
+; void* x64CrashCFITest_Launcher(int getAddress, void* pTestFn)
+x64CrashCFITest_Launcher proc frame
+  TestHeader
+
+  .endprolog
+  call rdx
+  ret
+x64CrashCFITest_Launcher endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_NO_MANS_LAND(uint64_t mode);
+; Not meant to be called. Only when mode = 1 in order to return its address.
+; Place this function's address on the stack so the stack scanning algorithm
+; thinks this is a return address, and places it on the stack trace.
+x64CrashCFITest_NO_MANS_LAND proc frame
+  TestHeader
+  .endprolog
+  ret
+x64CrashCFITest_NO_MANS_LAND endp
+
+PaddingBetweenFunctions
+
+; Test that we:
+; - handle unknown opcodes gracefully
+; - fall back to other stack unwind strategies if CFI doesn't work
+;
+; In order to properly unwind this frame, we'd need to fully support
+; SET_FPREG with offsets, plus restoring registers via PUSH_NONVOL.
+; To do this, sprinkle the stack with bad return addresses
+; and stack pointers.
+x64CrashCFITest_UnknownOpcode proc frame
+  TestHeader
+
+  push rax
+  .allocstack 8
+
+  push rbp
+  .pushreg rbp
+
+  push rax
+  push rsp
+  push rax
+  push rsp
+  .allocstack 20h
+  ; rsp = [rsp][pfn][rsp][pfn][rbp][pfn][ra]
+
+  lea rbp, [rsp+10h]
+  .setframe rbp, 10h
+  ; rsp = [rsp][pfn] [rsp][pfn][rbp][pfn][ra]
+  ; rbp =           ^
+
+  .endprolog
+
+  ; Now modify RSP so measuring stack size from unwind ops will not help
+  ; finding the return address.
+  push rax
+  push rsp
+  ; rsp = [rsp][pfn][rsp][pfn] [rsp][pfn][rbp][pfn][ra]
+
+  DoCrash
+
+x64CrashCFITest_UnknownOpcode endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_PUSH_NONVOL(uint64_t mode);
+;
+; Test correct handling of PUSH_NONVOL unwind code.
+;
+x64CrashCFITest_PUSH_NONVOL proc frame
+  TestHeader
+
+  push r10
+  .pushreg r10
+  push r15
+  .pushreg r15
+  push rbx
+  .pushreg rbx
+  push rsi
+  .pushreg rsi
+  push rbp
+  .pushreg rbp
+  ; rsp = [rbp][rsi][rbx][r15][r10][ra]
+
+  push rax
+  .allocstack 8
+  ; rsp = [pfn][rbp][rsi][rbx][r15][r10][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_PUSH_NONVOL endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_ALLOC_SMALL(uint64_t mode);
+;
+; Small allocations are between 8bytes and 512kb-8bytes
+;
+x64CrashCFITest_ALLOC_SMALL proc frame
+  TestHeader
+
+  ; Trash rbp to force stack scan. This will force
+  ; correct behavior for test_crash_win64cfi_not_a_pe, et al.
+  xor rbp, rbp
+
+  push rax
+  push rax
+  push rax
+  push rax
+  .allocstack 20h
+  ; rsp = [pfn][pfn][pfn][pfn][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_ALLOC_SMALL endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_ALLOC_LARGE(uint64_t mode);
+;
+; Allocations between 512kb and 4gb
+; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack
+; space for this.
+x64CrashCFITest_ALLOC_LARGE proc frame
+  TestHeader
+
+  sub rsp, 0a000h
+  .allocstack 0a000h
+  ; rsp = [..640kb..][ra]
+
+  mov qword ptr [rsp], rax
+  ; rsp = [pfn][..640kb-8..][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_ALLOC_LARGE endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_SAVE_NONVOL(uint64_t mode);
+;
+; Test correct handling of SAVE_NONVOL unwind code.
+;
+x64CrashCFITest_SAVE_NONVOL proc frame
+  TestHeader
+
+  sub rsp, 30h
+  .allocstack 30h
+  ; rsp = [..30..][ra]
+
+  mov qword ptr [rsp+28h], r10
+  .savereg r10, 28h
+  mov qword ptr [rsp+20h], rbp
+  .savereg rbp, 20h
+  mov qword ptr [rsp+18h], rsi
+  .savereg rsi, 18h
+  mov qword ptr [rsp+10h], rbx
+  .savereg rbx, 10h
+  mov qword ptr [rsp+8], r15
+  .savereg r15, 8
+  ; rsp = [r15][rbx][rsi][rbp][r10][ra]
+
+  mov qword ptr [rsp], rax
+
+  ; rsp = [pfn][r15][rbx][rsi][rbp][r10][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_SAVE_NONVOL endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_SAVE_NONVOL_FAR(uint64_t mode);
+;
+; Similar to the test above but adding 640kb to most offsets.
+; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack
+; space for this.
+x64CrashCFITest_SAVE_NONVOL_FAR proc frame
+  TestHeader
+
+  sub rsp, 0a0030h
+  .allocstack 0a0030h
+  ; rsp = [..640k..][..30..][ra]
+
+  mov qword ptr [rsp+28h+0a0000h], r10
+  .savereg r10, 28h+0a0000h
+  mov qword ptr [rsp+20h+0a0000h], rbp
+  .savereg rbp, 20h+0a0000h
+  mov qword ptr [rsp+18h+0a0000h], rsi
+  .savereg rsi, 18h+0a0000h
+  mov qword ptr [rsp+10h+0a0000h], rbx
+  .savereg rbx, 10h+0a0000h
+  mov qword ptr [rsp+8+0a0000h], r15
+  .savereg r15, 8+0a0000h
+  ; rsp = [..640k..][..8..][r15][rbx][rsi][rbp][r10][ra]
+
+  mov qword ptr [rsp], rax
+
+  ; rsp = [pfn][..640k..][r15][rbx][rsi][rbp][r10][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_SAVE_NONVOL_FAR endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_SAVE_XMM128(uint64_t mode);
+;
+; Test correct handling of SAVE_XMM128 unwind code.
+x64CrashCFITest_SAVE_XMM128 proc frame
+  TestHeader
+
+  sub rsp, 30h
+  .allocstack 30h
+  ; rsp = [..30..][ra]
+
+  movdqu [rsp+20h], xmm6
+  .savexmm128 xmm6, 20h
+  ; rsp = [..20..][xmm6][ra]
+
+  movdqu [rsp+10h], xmm15
+  .savexmm128 xmm15, 10h
+  ; rsp = [..10..][xmm15][xmm6][ra]
+
+  mov qword ptr [rsp], rax
+  ; rsp = [pfn][..8..][xmm15][xmm6][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_SAVE_XMM128 endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_SAVE_XMM128(uint64_t mode);
+;
+; Similar to the test above but adding 640kb to most offsets.
+; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack
+; space for this.
+x64CrashCFITest_SAVE_XMM128_FAR proc frame
+  TestHeader
+
+  sub rsp, 0a0030h
+  .allocstack 0a0030h
+  ; rsp = [..640kb..][..30..][ra]
+
+  movdqu [rsp+20h+0a0000h], xmm6
+  .savexmm128 xmm6, 20h+0a0000h
+  ; rsp = [..640kb..][..20..][xmm6][ra]
+
+  movdqu [rsp+10h+0a0000h], xmm6
+  .savexmm128 xmm15, 10h+0a0000h
+  ; rsp = [..640kb..][..10..][xmm15][xmm6][ra]
+
+  mov qword ptr [rsp], rax
+  ; rsp = [pfn][..640kb..][..8..][xmm15][xmm6][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_SAVE_XMM128_FAR endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_EPILOG(uint64_t mode);
+;
+; The epilog unwind op will also set the unwind version to 2.
+; Test that we don't choke on UWOP_EPILOG or version 2 unwind info.
+x64CrashCFITest_EPILOG proc frame
+  TestHeader
+
+  push rax
+  .allocstack 8
+  ; rsp = [pfn][ra]
+
+  .endprolog
+
+  DoCrash
+
+  .beginepilog
+
+  ret
+
+x64CrashCFITest_EPILOG endp
+
+PaddingBetweenFunctions
+
+; Having an EOF symbol at the end of this file contains symbolication to this
+; file. So addresses beyond this file don't get mistakenly symbolicated as a
+; meaningful function name.
+x64CrashCFITest_EOF proc frame
+  TestHeader
+  .endprolog
+  ret
+x64CrashCFITest_EOF endp
+
+end
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -1442,17 +1442,17 @@ StreamTaskTracer(PSLockRef aLock, Splice
 }
 
 static void
 StreamMetaJSCustomObject(PSLockRef aLock, SpliceableJSONWriter& aWriter,
                          const TimeStamp& aShutdownTime)
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists() && ActivePS::Exists(aLock));
 
-  aWriter.IntProperty("version", 8);
+  aWriter.IntProperty("version", 9);
 
   // The "startTime" field holds the number of milliseconds since midnight
   // January 1, 1970 GMT. This grotty code computes (Now - (Now -
   // ProcessStartTime)) to convert CorePS::ProcessStartTime() into that form.
   TimeDuration delta = TimeStamp::Now() - CorePS::ProcessStartTime();
   aWriter.DoubleProperty(
     "startTime", static_cast<double>(PR_Now()/1000.0 - delta.ToMilliseconds()));
 
--- a/xpcom/base/CycleCollectedJSContext.cpp
+++ b/xpcom/base/CycleCollectedJSContext.cpp
@@ -365,25 +365,58 @@ CycleCollectedJSContext::AfterProcessTas
       Promise::PerformMicroTaskCheckpoint();
     } else {
       Promise::PerformWorkerMicroTaskCheckpoint();
     }
   }
 
   // Step 4.2 Execute any events that were waiting for a stable state.
   ProcessStableStateQueue();
+
+  // This should be a fast test so that it won't affect the next task processing.
+  IsIdleGCTaskNeeded();
 }
 
 void
 CycleCollectedJSContext::AfterProcessMicrotask()
 {
   MOZ_ASSERT(mJSContext);
   AfterProcessMicrotask(RecursionDepth());
 }
 
+void CycleCollectedJSContext::IsIdleGCTaskNeeded()
+{
+  class IdleTimeGCTaskRunnable : public mozilla::IdleRunnable
+  {
+  public:
+    using mozilla::IdleRunnable::IdleRunnable;
+
+  public:
+    NS_IMETHOD Run() override
+    {
+      CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get();
+      if (ccrt) {
+        ccrt->RunIdleTimeGCTask();
+      }
+      return NS_OK;
+    }
+
+    nsresult Cancel() override
+    {
+      return NS_OK;
+    }
+  };
+
+  if (Runtime()->IsIdleGCTaskNeeded()) {
+    nsCOMPtr<nsIRunnable> gc_task = new IdleTimeGCTaskRunnable();
+    NS_IdleDispatchToCurrentThread(gc_task.forget());
+    Runtime()->SetPendingIdleGCTask();
+  }
+}
+
 void
 CycleCollectedJSContext::AfterProcessMicrotask(uint32_t aRecursionDepth)
 {
   MOZ_ASSERT(mJSContext);
 
   // Between microtasks, execute any events that were waiting for a microtask
   // to complete.
   ProcessMetastableStateQueue(aRecursionDepth);
--- a/xpcom/base/CycleCollectedJSContext.h
+++ b/xpcom/base/CycleCollectedJSContext.h
@@ -189,16 +189,19 @@ protected:
 public:
   // nsThread entrypoints
   virtual void BeforeProcessTask(bool aMightBlock) { };
   virtual void AfterProcessTask(uint32_t aRecursionDepth);
 
   // microtask processor entry point
   void AfterProcessMicrotask();
 
+  // Check whether we need an idle GC task.
+  void IsIdleGCTaskNeeded();
+
   uint32_t RecursionDepth();
 
   // Run in stable state (call through nsContentUtils)
   void RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable);
   // This isn't in the spec at all yet, but this gets the behavior we want for IDB.
   // Runs after the current microtask completes.
   void RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable);
 
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -505,16 +505,17 @@ MozCrashWarningReporter(JSContext*, JSEr
 {
   MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?");
 }
 
 CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSContext* aCx)
   : mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal)
   , mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal)
   , mJSRuntime(JS_GetRuntime(aCx))
+  , mHasPendingIdleGCTask(false)
   , mPrevGCSliceCallback(nullptr)
   , mPrevGCNurseryCollectionCallback(nullptr)
   , mJSHolderMap(256)
   , mOutOfMemoryState(OOMState::OK)
   , mLargeAllocationFailureState(OOMState::OK)
 {
   MOZ_COUNT_CTOR(CycleCollectedJSRuntime);
   MOZ_ASSERT(aCx);
--- a/xpcom/base/CycleCollectedJSRuntime.h
+++ b/xpcom/base/CycleCollectedJSRuntime.h
@@ -242,16 +242,44 @@ public:
   void SetLargeAllocationFailure(OOMState aNewState);
 
   void AnnotateAndSetOutOfMemory(OOMState* aStatePtr, OOMState aNewState);
   void OnGC(JSContext* aContext, JSGCStatus aStatus);
   void OnOutOfMemory();
   void OnLargeAllocationFailure();
 
   JSRuntime* Runtime() { return mJSRuntime; }
+  const JSRuntime* Runtime() const { return mJSRuntime; }
+
+  bool HasPendingIdleGCTask() const
+  {
+    // Idle GC task associates with JSRuntime.
+    MOZ_ASSERT_IF(mHasPendingIdleGCTask, Runtime());
+    return mHasPendingIdleGCTask;
+  }
+  void SetPendingIdleGCTask()
+  {
+    // Idle GC task associates with JSRuntime.
+    MOZ_ASSERT(Runtime());
+    mHasPendingIdleGCTask = true;
+  }
+  void ClearPendingIdleGCTask() { mHasPendingIdleGCTask = false; }
+
+  void RunIdleTimeGCTask()
+  {
+    if (HasPendingIdleGCTask()) {
+      JS::RunIdleTimeGCTask(Runtime());
+      ClearPendingIdleGCTask();
+    }
+  }
+
+  bool IsIdleGCTaskNeeded()
+  {
+    return !HasPendingIdleGCTask() && Runtime() && JS::IsIdleGCTaskNeeded(Runtime());
+  }
 
 public:
   void AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer);
   void RemoveJSHolder(void* aHolder);
 #ifdef DEBUG
   bool IsJSHolder(void* aHolder);
   void AssertNoObjectsToTrace(void* aPossibleJSHolder);
 #endif
@@ -303,16 +331,17 @@ public:
 private:
   LinkedList<CycleCollectedJSContext> mContexts;
 
   JSGCThingParticipant mGCThingCycleCollectorGlobal;
 
   JSZoneParticipant mJSZoneCycleCollectorGlobal;
 
   JSRuntime* mJSRuntime;
+  bool mHasPendingIdleGCTask;
 
   JS::GCSliceCallback mPrevGCSliceCallback;
   JS::GCNurseryCollectionCallback mPrevGCNurseryCollectionCallback;
 
   mozilla::TimeStamp mLatestNurseryCollectionStart;
 
   SegmentedVector<JSHolderInfo, 1024, InfallibleAllocPolicy> mJSHolders;
   nsDataHashtable<nsPtrHashKey<void>, JSHolderInfo*> mJSHolderMap;