merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 06 Oct 2014 15:32:37 +0200
changeset 232149 c2c6ea9cc42854b4e539a04c6cead7414edf4986
parent 232107 e0d714f43edc0363136d7f563aecc6bdc83482bd (current diff)
parent 232148 539cf3404ad2985f2d86feee0638766232ec387a (diff)
child 232158 e0d9ad4be6065c9d49f4712a4a06250f4b15cbee
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone35.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 a=merge
memory/replace/dmd/test-expected.dmd
--- a/build/Makefile.in
+++ b/build/Makefile.in
@@ -93,25 +93,32 @@ GARBAGE_DIRS += $(_VALGRIND_DIR)
 		$(topsrcdir)/build/valgrind/i386-redhat-linux-gnu.sup \
 		$(topsrcdir)/build/valgrind/x86_64-redhat-linux-gnu.sup \
 		$(NULL)
 
 libs:: $(_VALGRIND_FILES)
 	$(INSTALL) $^ $(_VALGRIND_DIR)
 endif
 
-ifdef ENABLE_TESTS
+ifneq (,$(ENABLE_TESTS)$(MOZ_DMD))
 libs:: $(topsrcdir)/tools/rb/fix_stack_using_bpsyms.py
 	$(INSTALL) $< $(DIST)/bin
 
 ifeq ($(OS_ARCH),Darwin)
 libs:: $(topsrcdir)/tools/rb/fix_macosx_stack.py
 	$(INSTALL) $< $(DIST)/bin
 endif
 
 ifeq ($(OS_ARCH),Linux)
 libs:: $(topsrcdir)/tools/rb/fix_linux_stack.py
 	$(INSTALL) $< $(DIST)/bin
 endif
+endif # ENABLE_TESTS or MOZ_DMD
 
+ifdef ENABLE_TESTS
 GARBAGE += $(srcdir)/automationutils.pyc
+endif # ENABLE_TESTS
 
-endif # ENABLE_TESTS
+ifdef MOZ_DMD
+libs:: $(topsrcdir)/memory/replace/dmd/dmd.py
+	$(INSTALL) $< $(DIST)/bin
+endif
+
--- a/config/external/nss/nss.def
+++ b/config/external/nss/nss.def
@@ -511,20 +511,22 @@ SECKEY_DecodeDERSubjectPublicKeyInfo
 SECKEY_DestroyPrivateKey
 SECKEY_DestroyPrivateKeyList
 SECKEY_DestroyPublicKey
 SECKEY_DestroySubjectPublicKeyInfo
 SECKEY_ECParamsToBasePointOrderLen
 SECKEY_ECParamsToKeySize
 SECKEY_EncodeDERSubjectPublicKeyInfo
 SECKEY_ExtractPublicKey
+SECKEY_GetPublicKeyType
 SECKEY_ImportDERPublicKey
 SECKEY_PublicKeyStrength
 SECKEY_PublicKeyStrengthInBits
 SECKEY_RSAPSSParamsTemplate DATA
+SECKEY_SignatureLen
 SECMIME_DecryptionAllowed
 SECMOD_AddNewModule
 SECMOD_AddNewModuleEx
 SECMOD_CancelWait
 SECMOD_CanDeleteInternalModule
 SECMOD_CloseUserDB
 SECMOD_CreateModule
 SECMOD_DeleteInternalModule
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -1057,23 +1057,16 @@ void HTMLMediaElement::UpdatePreloadActi
       }
     } else {
       // Use the suggested "missing value default" of "metadata", or the value
       // specified by the media.preload.default, if present.
       nextAction = static_cast<PreloadAction>(preloadDefault);
     }
   }
 
-  if ((mBegun || mIsRunningSelectResource) && nextAction < mPreloadAction) {
-    // We've started a load or are already downloading, and the preload was
-    // changed to a state where we buffer less. We don't support this case,
-    // so don't change the preload behaviour.
-    return;
-  }
-
   mPreloadAction = nextAction;
   if (nextAction == HTMLMediaElement::PRELOAD_ENOUGH) {
     if (mSuspendedForPreloadNone) {
       // Our load was previouly suspended due to the media having preload
       // value "none". The preload value has changed to preload:auto, so
       // resume the load.
       ResumeLoad(PRELOAD_ENOUGH);
     } else {
--- a/content/media/MediaDecoderReader.h
+++ b/content/media/MediaDecoderReader.h
@@ -83,16 +83,19 @@ public:
   // If aSkipToKeyframe is true, the decode should skip ahead to the
   // the next keyframe at or after aTimeThreshold microseconds.
   virtual void RequestVideoData(bool aSkipToNextKeyframe,
                                 int64_t aTimeThreshold);
 
   virtual bool HasAudio() = 0;
   virtual bool HasVideo() = 0;
 
+  // A function that is called before ReadMetadata() call.
+  virtual void PreReadMetadata() {};
+
   // Read header data for all bitstreams in the file. Fills aInfo with
   // the data required to present the media, and optionally fills *aTags
   // with tag metadata from the file.
   // Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) = 0;
 
   // Moves the decode head to aTime microseconds. aStartTime and aEndTime
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -1416,21 +1416,31 @@ void MediaDecoderStateMachine::StartWait
   AssertCurrentThreadInMonitor();
   SetState(DECODER_STATE_WAIT_FOR_RESOURCES);
   DECODER_LOG("StartWaitForResources");
 }
 
 void MediaDecoderStateMachine::NotifyWaitingForResourcesStatusChanged()
 {
   AssertCurrentThreadInMonitor();
-  if (mState != DECODER_STATE_WAIT_FOR_RESOURCES ||
-      mReader->IsWaitingMediaResources()) {
+  DECODER_LOG("NotifyWaitingForResourcesStatusChanged");
+  RefPtr<nsIRunnable> task(
+    NS_NewRunnableMethod(this,
+      &MediaDecoderStateMachine::DoNotifyWaitingForResourcesStatusChanged));
+  mDecodeTaskQueue->Dispatch(task);
+}
+
+void MediaDecoderStateMachine::DoNotifyWaitingForResourcesStatusChanged()
+{
+  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  if (mState != DECODER_STATE_WAIT_FOR_RESOURCES) {
     return;
   }
-  DECODER_LOG("NotifyWaitingForResourcesStatusChanged");
+  DECODER_LOG("DoNotifyWaitingForResourcesStatusChanged");
   // The reader is no longer waiting for resources (say a hardware decoder),
   // we can now proceed to decode metadata.
   SetState(DECODER_STATE_DECODING_NONE);
   ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::Play()
 {
@@ -1905,16 +1915,18 @@ MediaDecoderStateMachine::CallDecodeMeta
 
 nsresult MediaDecoderStateMachine::DecodeMetadata()
 {
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
   DECODER_LOG("Decoding Media Headers");
 
+  mReader->PreReadMetadata();
+
   if (mReader->IsWaitingMediaResources()) {
     StartWaitForResources();
     return NS_OK;
   }
 
   nsresult res;
   MediaInfo info;
   {
--- a/content/media/MediaDecoderStateMachine.h
+++ b/content/media/MediaDecoderStateMachine.h
@@ -322,19 +322,19 @@ public:
   bool IsShutdown();
 
   void QueueMetadata(int64_t aPublishTime, MediaInfo* aInfo, MetadataTags* aTags);
 
   // Returns true if we're currently playing. The decoder monitor must
   // be held.
   bool IsPlaying();
 
+  // Dispatch DoNotifyWaitingForResourcesStatusChanged task to mDecodeTaskQueue.
   // Called when the reader may have acquired the hardware resources required
-  // to begin decoding. The state machine may move into DECODING_METADATA if
-  // appropriate. The decoder monitor must be held while calling this.
+  // to begin decoding. The decoder monitor must be held while calling this.
   void NotifyWaitingForResourcesStatusChanged();
 
   // Notifies the state machine that should minimize the number of samples
   // decoded we preroll, until playback starts. The first time playback starts
   // the state machine is free to return to prerolling normally. Note
   // "prerolling" in this context refers to when we decode and buffer decoded
   // samples in advance of when they're needed for playback.
   void SetMinimizePrerollUntilPlaybackStarts();
@@ -634,16 +634,20 @@ protected:
 
   // Called by the AudioSink to signal that all outstanding work is complete
   // and the sink is shutting down.
   void OnAudioSinkComplete();
 
   // Called by the AudioSink to signal errors.
   void OnAudioSinkError();
 
+  // The state machine may move into DECODING_METADATA if we are in
+  // DECODER_STATE_WAIT_FOR_RESOURCES.
+  void DoNotifyWaitingForResourcesStatusChanged();
+
   // The decoder object that created this state machine. The state machine
   // holds a strong reference to the decoder to ensure that the decoder stays
   // alive once media element has started the decoder shutdown process, and has
   // dropped its reference to the decoder. This enables the state machine to
   // keep using the decoder's monitor until the state machine has finished
   // shutting down, without fear of the monitor being destroyed. After
   // shutting down, the state machine will then release this reference,
   // causing the decoder to be destroyed. This is accessed on the decode,
--- a/content/media/MediaResource.cpp
+++ b/content/media/MediaResource.cpp
@@ -78,17 +78,16 @@ ChannelMediaResource::ChannelMediaResour
                                            nsIURI* aURI,
                                            const nsACString& aContentType)
   : BaseMediaResource(aDecoder, aChannel, aURI, aContentType),
     mOffset(0), mSuspendCount(0),
     mReopenOnError(false), mIgnoreClose(false),
     mCacheStream(MOZ_THIS_IN_INITIALIZER_LIST()),
     mLock("ChannelMediaResource.mLock"),
     mIgnoreResume(false),
-    mSeekingForMetadata(false),
     mIsTransportSeekable(true)
 {
 #ifdef PR_LOGGING
   if (!gMediaResourceLog) {
     gMediaResourceLog = PR_NewLogModule("MediaResource");
   }
 #endif
 }
@@ -353,21 +352,17 @@ ChannelMediaResource::OnStartRequest(nsI
 
   {
     MutexAutoLock lock(mLock);
     mIsTransportSeekable = seekable;
     mChannelStatistics->Start();
   }
 
   mReopenOnError = false;
-  // If we are seeking to get metadata, because we are playing an OGG file,
-  // ignore if the channel gets closed without us suspending it explicitly. We
-  // don't want to tell the element that the download has finished whereas we
-  // just happended to have reached the end of the media while seeking.
-  mIgnoreClose = mSeekingForMetadata;
+  mIgnoreClose = false;
 
   if (mSuspendCount > 0) {
     // Re-suspend the channel if it needs to be suspended
     // No need to call PossiblySuspend here since the channel is
     // definitely in the right state for us in OnStartRequest.
     mChannel->Suspend();
     mIgnoreResume = false;
   }
@@ -803,26 +798,16 @@ nsresult ChannelMediaResource::Seek(int3
 {
   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
 
   CMLOG("Seek requested for aOffset [%lld] for decoder [%p]",
         aOffset, mDecoder);
   return mCacheStream.Seek(aWhence, aOffset);
 }
 
-void ChannelMediaResource::StartSeekingForMetadata()
-{
-  mSeekingForMetadata = true;
-}
-
-void ChannelMediaResource::EndSeekingForMetadata()
-{
-  mSeekingForMetadata = false;
-}
-
 int64_t ChannelMediaResource::Tell()
 {
   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
 
   return mCacheStream.Tell();
 }
 
 nsresult ChannelMediaResource::GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
@@ -1226,18 +1211,16 @@ public:
 
   // Other thread
   virtual void     SetReadMode(MediaCacheStream::ReadMode aMode) {}
   virtual void     SetPlaybackRate(uint32_t aBytesPerSecond) {}
   virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
   virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
                           uint32_t aCount, uint32_t* aBytes);
   virtual nsresult Seek(int32_t aWhence, int64_t aOffset);
-  virtual void     StartSeekingForMetadata() {};
-  virtual void     EndSeekingForMetadata() {};
   virtual int64_t  Tell();
 
   // Any thread
   virtual void    Pin() {}
   virtual void    Unpin() {}
   virtual double  GetDownloadRate(bool* aIsReliable)
   {
     // The data's all already here
--- a/content/media/MediaResource.h
+++ b/content/media/MediaResource.h
@@ -306,18 +306,16 @@ public:
   //
   // The default strategy does not do any seeking - the only issue is
   // a blocked read which it handles by causing the listener to close
   // the pipe, as per the http case.
   //
   // The file strategy doesn't block for any great length of time so
   // is fine for a no-op cancel.
   virtual nsresult Seek(int32_t aWhence, int64_t aOffset) = 0;
-  virtual void StartSeekingForMetadata() = 0;
-  virtual void EndSeekingForMetadata() = 0;
   // Report the current offset in bytes from the start of the stream.
   virtual int64_t Tell() = 0;
   // Moves any existing channel loads into the background, so that they don't
   // block the load event. Any new loads initiated (for example to seek)
   // will also be in the background.
   virtual void MoveLoadsToBackground() {}
   // Ensures that the value returned by IsSuspendedByCache below is up to date
   // (i.e. the cache has examined this stream at least once).
@@ -583,18 +581,16 @@ public:
 
   // Other thread
   virtual void     SetReadMode(MediaCacheStream::ReadMode aMode);
   virtual void     SetPlaybackRate(uint32_t aBytesPerSecond);
   virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
   virtual nsresult ReadAt(int64_t offset, char* aBuffer,
                           uint32_t aCount, uint32_t* aBytes);
   virtual nsresult Seek(int32_t aWhence, int64_t aOffset);
-  virtual void     StartSeekingForMetadata();
-  virtual void     EndSeekingForMetadata();
   virtual int64_t  Tell();
 
   // Any thread
   virtual void    Pin();
   virtual void    Unpin();
   virtual double  GetDownloadRate(bool* aIsReliable);
   virtual int64_t GetLength();
   virtual int64_t GetNextCachedData(int64_t aOffset);
@@ -709,19 +705,16 @@ protected:
   Mutex               mLock;
   nsRefPtr<MediaChannelStatistics> mChannelStatistics;
 
   // True if we couldn't suspend the stream and we therefore don't want
   // to resume later. This is usually due to the channel not being in the
   // isPending state at the time of the suspend request.
   bool mIgnoreResume;
 
-  // True if we are seeking to get the real duration of the file.
-  bool mSeekingForMetadata;
-
   // Start and end offset of the bytes to be requested.
   MediaByteRange mByteRange;
 
   // True if the stream can seek into unbuffered ranged, i.e. if the
   // connection supports byte range requests.
   bool mIsTransportSeekable;
 };
 
--- a/content/media/RtspMediaResource.h
+++ b/content/media/RtspMediaResource.h
@@ -126,20 +126,16 @@ public:
   MOZ_OVERRIDE {
     return NS_OK;
   }
   // dummy
   virtual nsresult Seek(int32_t aWhence, int64_t aOffset) MOZ_OVERRIDE {
     return NS_OK;
   }
   // dummy
-  virtual void     StartSeekingForMetadata() MOZ_OVERRIDE {}
-  // dummy
-  virtual void     EndSeekingForMetadata() MOZ_OVERRIDE {}
-  // dummy
   virtual int64_t  Tell() MOZ_OVERRIDE { return 0; }
 
   // Any thread
   virtual void    Pin() MOZ_OVERRIDE {}
   virtual void    Unpin() MOZ_OVERRIDE {}
 
   virtual bool    IsSuspendedByCache() MOZ_OVERRIDE { return mIsSuspend; }
 
--- a/content/media/mediasource/MediaSourceResource.h
+++ b/content/media/mediasource/MediaSourceResource.h
@@ -34,18 +34,16 @@ public:
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() MOZ_OVERRIDE { UNIMPLEMENTED(); return nullptr; }
   virtual bool CanClone() MOZ_OVERRIDE { UNIMPLEMENTED(); return false; }
   virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder) MOZ_OVERRIDE { UNIMPLEMENTED(); return nullptr; }
   virtual void SetReadMode(MediaCacheStream::ReadMode aMode) MOZ_OVERRIDE { UNIMPLEMENTED(); }
   virtual void SetPlaybackRate(uint32_t aBytesPerSecond) MOZ_OVERRIDE  { UNIMPLEMENTED(); }
   virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) MOZ_OVERRIDE { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
   virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) MOZ_OVERRIDE { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
   virtual nsresult Seek(int32_t aWhence, int64_t aOffset) MOZ_OVERRIDE { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
-  virtual void StartSeekingForMetadata() MOZ_OVERRIDE { UNIMPLEMENTED(); }
-  virtual void EndSeekingForMetadata() MOZ_OVERRIDE { UNIMPLEMENTED(); }
   virtual int64_t Tell() MOZ_OVERRIDE { UNIMPLEMENTED(); return -1; }
   virtual void Pin() MOZ_OVERRIDE { UNIMPLEMENTED(); }
   virtual void Unpin() MOZ_OVERRIDE { UNIMPLEMENTED(); }
   virtual double GetDownloadRate(bool* aIsReliable) MOZ_OVERRIDE { UNIMPLEMENTED(); *aIsReliable = false; return 0; }
   virtual int64_t GetLength() MOZ_OVERRIDE { UNIMPLEMENTED(); return -1; }
   virtual int64_t GetNextCachedData(int64_t aOffset) MOZ_OVERRIDE { UNIMPLEMENTED(); return -1; }
   virtual int64_t GetCachedDataEnd(int64_t aOffset) MOZ_OVERRIDE { UNIMPLEMENTED(); return -1; }
   virtual bool IsDataCachedToEndOfResource(int64_t aOffset) MOZ_OVERRIDE { UNIMPLEMENTED(); return false; }
--- a/content/media/mediasource/SourceBufferResource.h
+++ b/content/media/mediasource/SourceBufferResource.h
@@ -52,18 +52,16 @@ public:
   virtual void Resume() MOZ_OVERRIDE { UNIMPLEMENTED(); }
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() MOZ_OVERRIDE { UNIMPLEMENTED(); return nullptr; }
   virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder) MOZ_OVERRIDE { UNIMPLEMENTED(); return nullptr; }
   virtual void SetReadMode(MediaCacheStream::ReadMode aMode) MOZ_OVERRIDE { UNIMPLEMENTED(); }
   virtual void SetPlaybackRate(uint32_t aBytesPerSecond) MOZ_OVERRIDE { UNIMPLEMENTED(); }
   virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) MOZ_OVERRIDE;
   virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) MOZ_OVERRIDE;
   virtual nsresult Seek(int32_t aWhence, int64_t aOffset) MOZ_OVERRIDE;
-  virtual void StartSeekingForMetadata() MOZ_OVERRIDE { UNIMPLEMENTED(); }
-  virtual void EndSeekingForMetadata() MOZ_OVERRIDE { UNIMPLEMENTED(); }
   virtual int64_t Tell() MOZ_OVERRIDE { return mOffset; }
   virtual void Pin() MOZ_OVERRIDE { UNIMPLEMENTED(); }
   virtual void Unpin() MOZ_OVERRIDE { UNIMPLEMENTED(); }
   virtual double GetDownloadRate(bool* aIsReliable) MOZ_OVERRIDE { UNIMPLEMENTED(); *aIsReliable = false; return 0; }
   virtual int64_t GetLength() MOZ_OVERRIDE { return mInputBuffer.GetLength(); }
   virtual int64_t GetNextCachedData(int64_t aOffset) MOZ_OVERRIDE {
     ReentrantMonitorAutoEnter mon(mMonitor);
     MOZ_ASSERT(aOffset >= 0);
--- a/content/media/ogg/OggReader.cpp
+++ b/content/media/ogg/OggReader.cpp
@@ -484,31 +484,29 @@ nsresult OggReader::ReadMetadata(MediaIn
     MediaResource* resource = mDecoder->GetResource();
     if (mDecoder->GetMediaDuration() == -1 &&
         !mDecoder->IsShutdown() &&
         resource->GetLength() >= 0 &&
         mDecoder->IsMediaSeekable())
     {
       // We didn't get a duration from the index or a Content-Duration header.
       // Seek to the end of file to find the end time.
-      mDecoder->GetResource()->StartSeekingForMetadata();
       int64_t length = resource->GetLength();
 
       NS_ASSERTION(length > 0, "Must have a content length to get end time");
 
       int64_t endTime = 0;
       {
         ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
         endTime = RangeEndTime(length);
       }
       if (endTime != -1) {
         mDecoder->SetMediaEndTime(endTime);
         LOG(PR_LOG_DEBUG, ("Got Ogg duration from seeking to end %lld", endTime));
       }
-      mDecoder->GetResource()->EndSeekingForMetadata();
     }
   } else {
     return NS_ERROR_FAILURE;
   }
   *aInfo = mInfo;
 
   return NS_OK;
 }
--- a/content/media/omx/MediaCodecReader.cpp
+++ b/content/media/omx/MediaCodecReader.cpp
@@ -280,16 +280,17 @@ MediaCodecReader::ProcessCachedDataTask:
 MediaCodecReader::MediaCodecReader(AbstractMediaDecoder* aDecoder)
   : MediaOmxCommonReader(aDecoder)
   , mColorConverterBufferSize(0)
   , mExtractor(nullptr)
   , mParserMonitor("MediaCodecReader::mParserMonitor")
   , mParseDataFromCache(true)
   , mNextParserPosition(INT64_C(0))
   , mParsedDataLength(INT64_C(0))
+  , mIsWaitingResources(false)
 {
   mHandler = new MessageHandler(this);
   mVideoListener = new VideoResourceListener(this);
 }
 
 MediaCodecReader::~MediaCodecReader()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
@@ -299,17 +300,24 @@ nsresult
 MediaCodecReader::Init(MediaDecoderReader* aCloneDonor)
 {
   return NS_OK;
 }
 
 bool
 MediaCodecReader::IsWaitingMediaResources()
 {
-  return mVideoTrack.mCodec != nullptr && !mVideoTrack.mCodec->allocated();
+  return mIsWaitingResources;
+}
+
+void
+MediaCodecReader::UpdateIsWaitingMediaResources()
+{
+  mIsWaitingResources = (mVideoTrack.mCodec != nullptr) &&
+                        (!mVideoTrack.mCodec->allocated());
 }
 
 bool
 MediaCodecReader::IsDormantNeeded()
 {
   return mVideoTrack.mSource != nullptr;
 }
 
@@ -642,30 +650,42 @@ MediaCodecReader::ParseDataSegment(const
     MOZ_ASSERT(mDecoder);
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mDecoder->UpdateEstimatedMediaDuration(duration);
   }
 
   return true;
 }
 
+void
+MediaCodecReader::PreReadMetadata()
+{
+  UpdateIsWaitingMediaResources();
+}
+
+
 nsresult
 MediaCodecReader::ReadMetadata(MediaInfo* aInfo,
                                MetadataTags** aTags)
 {
   MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   if (!ReallocateResources()) {
     return NS_ERROR_FAILURE;
   }
 
   if (!TriggerIncrementalParser()) {
     return NS_ERROR_FAILURE;
   }
 
+  // Bug 1050667, both MediaDecoderStateMachine and MediaCodecReader
+  // relies on IsWaitingMediaResources() function. And the waiting state will be
+  // changed by binder thread, so we store the waiting state in a cache value to
+  // make them in the same waiting state.
+  UpdateIsWaitingMediaResources();
   if (IsWaitingMediaResources()) {
     return NS_OK;
   }
 
   // TODO: start streaming
 
   if (!UpdateDuration()) {
     return NS_ERROR_FAILURE;
--- a/content/media/omx/MediaCodecReader.h
+++ b/content/media/omx/MediaCodecReader.h
@@ -73,16 +73,17 @@ public:
                                 int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   // Disptach a DecodeAduioDataTask to decode video data.
   virtual void RequestAudioData() MOZ_OVERRIDE;
 
   virtual bool HasAudio();
   virtual bool HasVideo();
 
+  virtual void PreReadMetadata() MOZ_OVERRIDE;
   // Read header data for all bitstreams in the file. Fills aInfo with
   // the data required to present the media, and optionally fills *aTags
   // with tag metadata from the file.
   // Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
 
   // Moves the decode head to aTime microseconds. aStartTime and aEndTime
@@ -147,17 +148,24 @@ protected:
 
   // Receive a notify from ResourceListener.
   // Called on Binder thread.
   virtual void codecReserved(Track& aTrack);
   virtual void codecCanceled(Track& aTrack);
 
   virtual bool CreateExtractor();
 
+  // Check the underlying HW resource is available and store the result in
+  // mIsWaitingResources.
+  void UpdateIsWaitingMediaResources();
+
   android::sp<android::MediaExtractor> mExtractor;
+  // A cache value updated by UpdateIsWaitingMediaResources(), makes the
+  // "waiting resources state" is synchronous to StateMachine.
+  bool mIsWaitingResources;
 
 private:
   // An intermediary class that can be managed by android::sp<T>.
   // Redirect onMessageReceived() to MediaCodecReader.
   class MessageHandler : public android::AHandler
   {
   public:
     MessageHandler(MediaCodecReader* aReader);
--- a/content/media/omx/MediaOmxReader.cpp
+++ b/content/media/omx/MediaOmxReader.cpp
@@ -142,16 +142,17 @@ MediaOmxReader::MediaOmxReader(AbstractM
   , mHasVideo(false)
   , mHasAudio(false)
   , mVideoSeekTimeUs(-1)
   , mAudioSeekTimeUs(-1)
   , mSkipCount(0)
   , mUseParserDuration(false)
   , mLastParserDuration(-1)
   , mIsShutdown(false)
+  , mIsWaitingResources(false)
 {
 #ifdef PR_LOGGING
   if (!gMediaDecoderLog) {
     gMediaDecoderLog = PR_NewLogModule("MediaDecoder");
   }
 #endif
 
   mAudioChannel = dom::AudioChannelService::GetDefaultAudioChannel();
@@ -183,20 +184,26 @@ void MediaOmxReader::Shutdown()
   ReleaseMediaResources();
   nsCOMPtr<nsIRunnable> event =
     NS_NewRunnableMethod(this, &MediaOmxReader::ReleaseDecoder);
   NS_DispatchToMainThread(event);
 }
 
 bool MediaOmxReader::IsWaitingMediaResources()
 {
-  if (!mOmxDecoder.get()) {
-    return false;
+  return mIsWaitingResources;
+}
+
+void MediaOmxReader::UpdateIsWaitingMediaResources()
+{
+  if (mOmxDecoder.get()) {
+    mIsWaitingResources = mOmxDecoder->IsWaitingMediaResources();
+  } else {
+    mIsWaitingResources = false;
   }
-  return mOmxDecoder->IsWaitingMediaResources();
 }
 
 bool MediaOmxReader::IsDormantNeeded()
 {
   if (!mOmxDecoder.get()) {
     return false;
   }
   return mOmxDecoder->IsDormantNeeded();
@@ -233,16 +240,21 @@ nsresult MediaOmxReader::InitOmxDecoder(
     mOmxDecoder = new OmxDecoder(mDecoder->GetResource(), mDecoder);
     if (!mOmxDecoder->Init(mExtractor)) {
       return NS_ERROR_FAILURE;
     }
   }
   return NS_OK;
 }
 
+void MediaOmxReader::PreReadMetadata()
+{
+  UpdateIsWaitingMediaResources();
+}
+
 nsresult MediaOmxReader::ReadMetadata(MediaInfo* aInfo,
                                       MetadataTags** aTags)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   EnsureActive();
 
   *aTags = nullptr;
 
@@ -256,23 +268,31 @@ nsresult MediaOmxReader::ReadMetadata(Me
   if (isMP3) {
     // When read sdcard's file on b2g platform at constructor,
     // the mDecoder->GetResource()->GetLength() would return -1.
     // Delay set the total duration on this function.
     mMP3FrameParser.SetLength(mDecoder->GetResource()->GetLength());
     ProcessCachedData(0, true);
   }
 
-  if (!mOmxDecoder->TryLoad()) {
+  if (!mOmxDecoder->AllocateMediaResources()) {
     return NS_ERROR_FAILURE;
   }
-
+  // Bug 1050667, both MediaDecoderStateMachine and MediaOmxReader
+  // relies on IsWaitingMediaResources() function. And the waiting state will be
+  // changed by binder thread, so we store the waiting state in a cache value to
+  // make them in consistent state.
+  UpdateIsWaitingMediaResources();
   if (IsWaitingMediaResources()) {
     return NS_OK;
   }
+  // After resources are available, set the metadata.
+  if (!mOmxDecoder->EnsureMetadata()) {
+    return NS_ERROR_FAILURE;
+  }
 
   if (isMP3 && mMP3FrameParser.IsMP3()) {
     int64_t duration = mMP3FrameParser.GetDuration();
     // The MP3FrameParser may reported a duration;
     // return -1 if no frame has been parsed.
     if (duration >= 0) {
       ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
       mUseParserDuration = true;
--- a/content/media/omx/MediaOmxReader.h
+++ b/content/media/omx/MediaOmxReader.h
@@ -42,26 +42,36 @@ class MediaOmxReader : public MediaOmxCo
   int64_t mLastParserDuration;
   int32_t mSkipCount;
   bool mUseParserDuration;
   bool mIsShutdown;
 protected:
   android::sp<android::OmxDecoder> mOmxDecoder;
   android::sp<android::MediaExtractor> mExtractor;
   MP3FrameParser mMP3FrameParser;
+
+  // A cache value updated by UpdateIsWaitingMediaResources(), makes the
+  // "waiting resources state" is synchronous to StateMachine.
+  bool mIsWaitingResources;
+
   // Called by ReadMetadata() during MediaDecoderStateMachine::DecodeMetadata()
   // on decode thread. It create and initialize the OMX decoder including
   // setting up custom extractor. The extractor provide the essential
   // information used for creating OMX decoder such as video/audio codec.
   virtual nsresult InitOmxDecoder();
 
   // Called inside DecodeVideoFrame, DecodeAudioData, ReadMetadata and Seek
   // to activate the decoder automatically.
   virtual void EnsureActive();
 
+  // Check the underlying HW resources are available and store the result in
+  // mIsWaitingResources. The result might be changed by binder thread,
+  // Can only called by ReadMetadata.
+  void UpdateIsWaitingMediaResources();
+
 public:
   MediaOmxReader(AbstractMediaDecoder* aDecoder);
   ~MediaOmxReader();
 
   virtual nsresult Init(MediaDecoderReader* aCloneDonor);
 
   virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
 
@@ -74,21 +84,23 @@ public:
     return mHasAudio;
   }
 
   virtual bool HasVideo()
   {
     return mHasVideo;
   }
 
-  virtual bool IsWaitingMediaResources();
+  // Return mIsWaitingResources.
+  virtual bool IsWaitingMediaResources() MOZ_OVERRIDE;
 
   virtual bool IsDormantNeeded();
   virtual void ReleaseMediaResources();
 
+  virtual void PreReadMetadata() MOZ_OVERRIDE;
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
   virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
   virtual void SetIdle() MOZ_OVERRIDE;
 
--- a/content/media/omx/OmxDecoder.cpp
+++ b/content/media/omx/OmxDecoder.cpp
@@ -157,29 +157,17 @@ bool OmxDecoder::Init(sp<MediaExtractor>
     // mAudioTrack is be used by OMXCodec. For offloaded audio track, using same
     // object gives undetermined behavior. So get a new track
     mAudioOffloadTrack = extractor->getTrack(audioTrackIndex);
 #endif
   }
   return true;
 }
 
-bool OmxDecoder::TryLoad() {
-
-  if (!AllocateMediaResources()) {
-    return false;
-  }
-
-  //check if video is waiting resources
-  if (mVideoSource.get()) {
-    if (mVideoSource->IsWaitingResources()) {
-      return true;
-    }
-  }
-
+bool OmxDecoder::EnsureMetadata() {
   // calculate duration
   int64_t totalDurationUs = 0;
   int64_t durationUs = 0;
   if (mVideoTrack.get() && mVideoTrack->getFormat()->findInt64(kKeyDuration, &durationUs)) {
     if (durationUs > totalDurationUs)
       totalDurationUs = durationUs;
   }
   if (mAudioTrack.get()) {
--- a/content/media/omx/OmxDecoder.h
+++ b/content/media/omx/OmxDecoder.h
@@ -147,19 +147,26 @@ public:
   // MediaExtractor::getTrackMetaData().
   // In general cases, the extractor is created by a sp<DataSource> which
   // connect to a MediaResource like ChannelMediaResource.
   // Data is read from the MediaResource to create a suitable extractor which
   // extracts data from a container.
   // Note: RTSP requires a custom extractor because it doesn't have a container.
   bool Init(sp<MediaExtractor>& extractor);
 
-  bool TryLoad();
   bool IsDormantNeeded();
+
+  // Called after resources(video/audio codec) are allocated, set the
+  // mDurationUs and video/audio metadata.
+  bool EnsureMetadata();
+
+  // Only called by MediaOmxDecoder, do not call this function arbitrarily.
+  // See bug 1050667.
   bool IsWaitingMediaResources();
+
   bool AllocateMediaResources();
   void ReleaseMediaResources();
   bool SetVideoFormat();
   bool SetAudioFormat();
 
   void ReleaseDecoder();
 
   void GetDuration(int64_t *durationUs) {
--- a/content/media/test/test_preload_actions.html
+++ b/content/media/test/test_preload_actions.html
@@ -381,44 +381,16 @@ var tests = [
       v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
       v.addEventListener("loadeddata", this.loadeddata, false);
       v.src = test.name; // Causes implicit load.
       document.body.appendChild(v);
     },
 
     name: "test11",
   },
-  /*{
-    // 12. Change preload value from auto to metadata after load started,
-    // should still do full load, should not halt after metadata only.
-    // disable this test since the spec is no longer found in the document
-    // http://dev.w3.org/html5/spec-preview/media-elements.html
-    canplaythrough:
-    function(e) {
-      var v = e.target;
-      is(v._gotLoadedMetaData, true, "(12) Must get loadedmetadata.");
-      is(v._gotLoadStart, true, "(12) Must get loadstart.");
-      maybeFinish(v, 12);
-    },
-
-    setup:
-    function(v) {
-      v._gotLoadStart = false;
-      v._gotLoadedMetaData = false;
-      v.preload = "auto";
-      v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
-      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
-      v.addEventListener("canplaythrough", this.canplaythrough, false);
-      v.src = test.name; // Causes implicit load.
-      document.body.appendChild(v);
-      v.preload = "metadata";
-    },
-
-    name: "test12",
-  },*/
   {
     // 13. Change preload value from auto to none after specifying a src
     // should load according to preload none, no buffering should have taken place
     suspend:
     function(e) {
       var v = e.target;
       is(v._gotLoadStart, true, "(13) Must get loadstart.");
       is(v._gotLoadedMetaData, false, "(13) Must not get loadedmetadata.");
@@ -563,38 +535,46 @@ var tests = [
       v.play(); // Should cause preload:none to be overridden.
     },
 
     name: "test18",
   },
   {
     // 19. Set preload='auto' on first video source then switching preload='none' and swapping the video source to another.
     // The second video should not start playing as it's preload state has been changed to 'none' from 'auto'
-    loadedmetadata: function(e) {
-      var v = e.target;
-      is(v.preload === "auto", true, "(19) preload is initially auto");
-      setTimeout(function() {
+    setup:
+    function(v) {
+      v.preload = "auto";
+      v.src = test.name;
+      // add a listener for when the video has loaded, so we know preload auto has worked
+      v.onloadedmetadata = function() {
+        is(v.preload, "auto", "(19) preload is initially auto");
         // set preload state to none and switch video sources
         v.preload="none";
         v.src = test.name + "?asdf";
-        setTimeout(function() {
-          is(v.readyState === 0, true, "(19) no buffering has taken place");
-          maybeFinish(v, 19);
-        }, 2000);
-      }, 2000);
 
-    },
+        v.onloadedmetadata = function() {
+          ok(false, "(19) 'loadedmetadata' shouldn't fire when preload is none");
+        }
 
-    setup:
-    function(v) {
-      var that = this;
-      v.preload = "auto";
-      v.src = test.name;
-      // add a listener for when the video has loaded, so we know preload auto has worked
-      v.addEventListener( "loadedmetadata", this.loadedmetadata, false);
+        var ontimeout = function() {
+          v.removeEventListener("suspend", onsuspend, false);
+          ok(false, "(19) 'suspend' should've fired");
+          maybeFinish(v, 19);
+        }
+        var cancel = setTimeout(ontimeout, 10000);
+
+        var onsuspend = function() {
+          v.removeEventListener("suspend", onsuspend, false);
+          clearTimeout(cancel);
+          is(v.readyState, 0, "(19) no buffering has taken place");
+          maybeFinish(v, 19);
+        }
+        v.addEventListener("suspend", onsuspend, false);
+      }
       document.body.appendChild(v);
     },
 
     name: "test19",
   }
 ];
 
 var iterationCount = 0;
--- a/content/media/webrtc/MediaEngineWebRTCVideo.cpp
+++ b/content/media/webrtc/MediaEngineWebRTCVideo.cpp
@@ -969,16 +969,18 @@ MediaEngineWebRTCVideoSource::OnTakePict
       new GenerateBlobRunnable(mPhotoCallbacks, aData, aLength, aMimeType));
   }
 }
 
 uint32_t
 MediaEngineWebRTCVideoSource::ConvertPixelFormatToFOURCC(int aFormat)
 {
   switch (aFormat) {
+  case HAL_PIXEL_FORMAT_RGBA_8888:
+    return libyuv::FOURCC_BGRA;
   case HAL_PIXEL_FORMAT_YCrCb_420_SP:
     return libyuv::FOURCC_NV21;
   case HAL_PIXEL_FORMAT_YV12:
     return libyuv::FOURCC_YV12;
   default: {
     LOG((" xxxxx Unknown pixel format %d", aFormat));
     MOZ_ASSERT(false, "Unknown pixel format.");
     return libyuv::FOURCC_ANY;
--- a/content/media/webspeech/recognition/SpeechGrammarList.cpp
+++ b/content/media/webspeech/recognition/SpeechGrammarList.cpp
@@ -3,43 +3,56 @@
 /* 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 "SpeechGrammarList.h"
 
 #include "mozilla/dom/SpeechGrammarListBinding.h"
 #include "mozilla/ErrorResult.h"
+#include "nsCOMPtr.h"
+#include "nsXPCOMStrings.h"
+#include "SpeechRecognition.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(SpeechGrammarList, mParent)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(SpeechGrammarList)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(SpeechGrammarList)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechGrammarList)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-SpeechGrammarList::SpeechGrammarList(nsISupports* aParent)
+SpeechGrammarList::SpeechGrammarList(nsISupports* aParent, nsISpeechRecognitionService* aRecognitionService)
   : mParent(aParent)
 {
+  this->mRecognitionService = aRecognitionService;
   SetIsDOMBinding();
 }
 
 SpeechGrammarList::~SpeechGrammarList()
 {
 }
 
-SpeechGrammarList*
+already_AddRefed<SpeechGrammarList>
 SpeechGrammarList::Constructor(const GlobalObject& aGlobal,
                                ErrorResult& aRv)
 {
-  return new SpeechGrammarList(aGlobal.GetAsSupports());
+  nsCOMPtr<nsISpeechRecognitionService> recognitionService;
+  recognitionService = GetSpeechRecognitionService();
+  if (!recognitionService) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  } else {
+    nsRefPtr<SpeechGrammarList> speechGrammarList =
+      new SpeechGrammarList(aGlobal.GetAsSupports(), recognitionService);
+    return speechGrammarList.forget();
+  }
 }
 
 JSObject*
 SpeechGrammarList::WrapObject(JSContext* aCx)
 {
   return SpeechGrammarListBinding::Wrap(aCx, this);
 }
 
@@ -71,17 +84,17 @@ SpeechGrammarList::AddFromURI(const nsAS
   return;
 }
 
 void
 SpeechGrammarList::AddFromString(const nsAString& aString,
                                  const Optional<float>& aWeight,
                                  ErrorResult& aRv)
 {
-  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+  mRecognitionService->ValidateAndSetGrammarList(this, nullptr);
   return;
 }
 
 already_AddRefed<SpeechGrammar>
 SpeechGrammarList::IndexedGetter(uint32_t aIndex, bool& aPresent,
                                  ErrorResult& aRv)
 {
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
--- a/content/media/webspeech/recognition/SpeechGrammarList.h
+++ b/content/media/webspeech/recognition/SpeechGrammarList.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_SpeechGrammarList_h
 #define mozilla_dom_SpeechGrammarList_h
 
 #include "mozilla/Attributes.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
+#include "nsISpeechRecognitionService.h"
 
 struct JSContext;
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
@@ -23,38 +24,39 @@ namespace dom {
 class GlobalObject;
 class SpeechGrammar;
 template<typename> class Optional;
 
 class SpeechGrammarList MOZ_FINAL : public nsISupports,
                                     public nsWrapperCache
 {
 public:
-  explicit SpeechGrammarList(nsISupports* aParent);
+  explicit SpeechGrammarList(nsISupports* aParent, nsISpeechRecognitionService* aRecognitionService);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(SpeechGrammarList)
 
-  SpeechGrammarList* Constructor(const GlobalObject& aGlobal,
-                                 ErrorResult& aRv);
+  static already_AddRefed<SpeechGrammarList> Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
 
   nsISupports* GetParentObject() const;
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   uint32_t Length() const;
 
   already_AddRefed<SpeechGrammar> Item(uint32_t aIndex, ErrorResult& aRv);
 
   void AddFromURI(const nsAString& aSrc, const Optional<float>& aWeight, ErrorResult& aRv);
 
   void AddFromString(const nsAString& aString, const Optional<float>& aWeight, ErrorResult& aRv);
 
   already_AddRefed<SpeechGrammar> IndexedGetter(uint32_t aIndex, bool& aPresent, ErrorResult& aRv);
 
+  nsCOMPtr<nsISpeechRecognitionService> mRecognitionService;
+
 private:
   ~SpeechGrammarList();
 
   nsCOMPtr<nsISupports> mParent;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/content/media/webspeech/recognition/SpeechRecognition.cpp
+++ b/content/media/webspeech/recognition/SpeechRecognition.cpp
@@ -52,16 +52,46 @@ GetSpeechRecognitionLog()
 
   return sLog;
 }
 #define SR_LOG(...) PR_LOG(GetSpeechRecognitionLog(), PR_LOG_DEBUG, (__VA_ARGS__))
 #else
 #define SR_LOG(...)
 #endif
 
+already_AddRefed<nsISpeechRecognitionService>
+GetSpeechRecognitionService()
+{
+  nsAutoCString speechRecognitionServiceCID;
+
+  nsAdoptingCString prefValue =
+  Preferences::GetCString(PREFERENCE_DEFAULT_RECOGNITION_SERVICE);
+  nsAutoCString speechRecognitionService;
+
+  if (!prefValue.get() || prefValue.IsEmpty()) {
+    speechRecognitionService = DEFAULT_RECOGNITION_SERVICE;
+  } else {
+    speechRecognitionService = prefValue;
+  }
+
+  if (!SpeechRecognition::mTestConfig.mFakeRecognitionService){
+    speechRecognitionServiceCID =
+      NS_LITERAL_CSTRING(NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX) +
+      speechRecognitionService;
+  } else {
+    speechRecognitionServiceCID =
+      NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX "fake";
+  }
+
+  nsresult aRv;
+  nsCOMPtr<nsISpeechRecognitionService> recognitionService;
+  recognitionService = do_GetService(speechRecognitionServiceCID.get(), &aRv);
+  return recognitionService.forget();
+}
+
 NS_INTERFACE_MAP_BEGIN(SpeechRecognition)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(SpeechRecognition, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(SpeechRecognition, DOMEventTargetHelper)
 
 struct SpeechRecognition::TestConfig SpeechRecognition::mTestConfig;
@@ -323,43 +353,16 @@ SpeechRecognition::ProcessAudioSegment(A
     samples += iterator->GetDuration();
     iterator.Next();
   }
 
   mRecognitionService->ProcessAudioSegment(aSegment, aTrackRate);
   return samples;
 }
 
-void
-SpeechRecognition::GetRecognitionServiceCID(nsACString& aResultCID)
-{
-  if (mTestConfig.mFakeRecognitionService) {
-    aResultCID =
-      NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX "fake";
-
-    return;
-  }
-
-  nsAdoptingCString prefValue =
-    Preferences::GetCString(PREFERENCE_DEFAULT_RECOGNITION_SERVICE);
-
-  nsAutoCString speechRecognitionService;
-  if (!prefValue.get() || prefValue.IsEmpty()) {
-    speechRecognitionService = DEFAULT_RECOGNITION_SERVICE;
-  } else {
-    speechRecognitionService = prefValue;
-  }
-
-  aResultCID =
-    NS_LITERAL_CSTRING(NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX) +
-    speechRecognitionService;
-
-  return;
-}
-
 /****************************************************************************
  * FSM Transition functions
  *
  * If a transition function may cause a DOM event to be fired,
  * it may also be re-entered, since the event handler may cause the
  * event loop to spin and new SpeechEvents to be processed.
  *
  * Rules:
@@ -686,23 +689,20 @@ SpeechRecognition::SetServiceURI(const n
 void
 SpeechRecognition::Start(const Optional<NonNull<DOMMediaStream>>& aStream, ErrorResult& aRv)
 {
   if (mCurrentState != STATE_IDLE) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
-  nsAutoCString speechRecognitionServiceCID;
-  GetRecognitionServiceCID(speechRecognitionServiceCID);
+  mRecognitionService = GetSpeechRecognitionService();
+  NS_ENSURE_TRUE_VOID(mRecognitionService);
 
   nsresult rv;
-  mRecognitionService = do_GetService(speechRecognitionServiceCID.get(), &rv);
-  NS_ENSURE_SUCCESS_VOID(rv);
-
   rv = mRecognitionService->Initialize(this);
   NS_ENSURE_SUCCESS_VOID(rv);
 
   MediaStreamConstraints constraints;
   constraints.mAudio.SetAsBoolean() = true;
 
   if (aStream.WasPassed()) {
     StartRecording(&aStream.Value());
--- a/content/media/webspeech/recognition/SpeechRecognition.h
+++ b/content/media/webspeech/recognition/SpeechRecognition.h
@@ -48,16 +48,18 @@ class SpeechEvent;
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* GetSpeechRecognitionLog();
 #define SR_LOG(...) PR_LOG(GetSpeechRecognitionLog(), PR_LOG_DEBUG, (__VA_ARGS__))
 #else
 #define SR_LOG(...)
 #endif
 
+already_AddRefed<nsISpeechRecognitionService> GetSpeechRecognitionService();
+
 class SpeechRecognition MOZ_FINAL : public DOMEventTargetHelper,
                                     public nsIObserver,
                                     public SupportsWeakPtr<SpeechRecognition>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_TYPENAME(SpeechRecognition)
   explicit SpeechRecognition(nsPIDOMWindow* aOwnerWindow);
 
@@ -229,18 +231,16 @@ private:
   void DoNothing(SpeechEvent* aEvent);
   void AbortSilently(SpeechEvent* aEvent);
   void AbortError(SpeechEvent* aEvent);
 
   nsRefPtr<DOMMediaStream> mDOMStream;
   nsRefPtr<SpeechStreamListener> mSpeechListener;
   nsCOMPtr<nsISpeechRecognitionService> mRecognitionService;
 
-  void GetRecognitionServiceCID(nsACString& aResultCID);
-
   FSMState mCurrentState;
 
   Endpointer mEndpointer;
   uint32_t mEstimationSamples;
 
   uint32_t mAudioSamplesPerChunk;
 
   // buffer holds one chunk of mAudioSamplesPerChunk
--- a/content/media/webspeech/recognition/nsISpeechRecognitionService.idl
+++ b/content/media/webspeech/recognition/nsISpeechRecognitionService.idl
@@ -2,24 +2,42 @@
 /* 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 "nsISupports.idl"
 
 %{C++
 #include "mozilla/WeakPtr.h"
+
+namespace mozilla {
+class AudioSegment;
+namespace dom {
+class SpeechRecognition;
+class SpeechRecognitionResultList;
+class SpeechGrammarList;
+class SpeechGrammar;
+}
+}
 %}
 
 native SpeechRecognitionWeakPtr(mozilla::WeakPtr<mozilla::dom::SpeechRecognition>);
 [ptr] native AudioSegmentPtr(mozilla::AudioSegment);
+[ptr] native SpeechGrammarListPtr(mozilla::dom::SpeechGrammarList);
+[ptr] native SpeechGrammarPtr(mozilla::dom::SpeechGrammar);
+
+[uuid(374583f0-4507-11e4-a183-164230d1df67)]
+interface nsISpeechGrammarCompilationCallback : nsISupports {
+    void grammarCompilationEnd(in SpeechGrammarPtr grammarObject, in boolean success);
+};
 
 [uuid(857f3fa2-a980-4d3e-a959-a2f53af74232)]
 interface nsISpeechRecognitionService : nsISupports {
     void initialize(in SpeechRecognitionWeakPtr aSpeechRecognition);
     void processAudioSegment(in AudioSegmentPtr aAudioSegment, in long aSampleRate);
+    void validateAndSetGrammarList(in SpeechGrammarListPtr aSpeechGramarList, in nsISpeechGrammarCompilationCallback aCallback);
     void soundEnd();
     void abort();
 };
 
 %{C++
 #define NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX "@mozilla.org/webspeech/service;1?name="
 %}
--- a/content/media/webspeech/recognition/test/FakeSpeechRecognitionService.cpp
+++ b/content/media/webspeech/recognition/test/FakeSpeechRecognitionService.cpp
@@ -47,16 +47,22 @@ FakeSpeechRecognitionService::ProcessAud
 
 NS_IMETHODIMP
 FakeSpeechRecognitionService::SoundEnd()
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
+FakeSpeechRecognitionService::ValidateAndSetGrammarList(mozilla::dom::SpeechGrammarList*, nsISpeechGrammarCompilationCallback*)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 FakeSpeechRecognitionService::Abort()
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 FakeSpeechRecognitionService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
 {
--- a/content/media/webspeech/recognition/test/FakeSpeechRecognitionService.h
+++ b/content/media/webspeech/recognition/test/FakeSpeechRecognitionService.h
@@ -4,27 +4,16 @@
  * 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 mozilla_dom_FakeSpeechRecognitionService_h
 #define mozilla_dom_FakeSpeechRecognitionService_h
 
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
-
-// nsISpeechRecognitionService needs these declarations
-namespace mozilla {
-  class AudioSegment;
-
-  namespace dom {
-    class SpeechRecognition;
-    class SpeechRecognitionResultList;
-  }
-}
-
 #include "nsISpeechRecognitionService.h"
 
 #define NS_FAKE_SPEECH_RECOGNITION_SERVICE_CID \
   {0x48c345e7, 0x9929, 0x4f9a, {0xa5, 0x63, 0xf4, 0x78, 0x22, 0x2d, 0xab, 0xcd}};
 
 namespace mozilla {
 
 class FakeSpeechRecognitionService : public nsISpeechRecognitionService,
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -238,13 +238,19 @@ public:
     void DeleteVertexArray(WebGLVertexArrayObject* vertexArray);
     bool IsVertexArray(WebGLVertexArrayObject* vertexArray);
     void BindVertexArray(WebGLVertexArrayObject* vertexArray);
 */
 
 private:
 
     WebGL2Context();
+
+    bool ValidateSizedInternalFormat(GLenum internalFormat, const char* info);
+    bool ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat,
+                                GLsizei width, GLsizei height, GLsizei depth,
+                                const char* info);
+
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/canvas/WebGL2ContextTextures.cpp
+++ b/dom/canvas/WebGL2ContextTextures.cpp
@@ -4,23 +4,177 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGL2Context.h"
 #include "GLContext.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
+bool
+WebGL2Context::ValidateSizedInternalFormat(GLenum internalformat, const char* info)
+{
+    switch (internalformat) {
+        // Sized Internal Formats
+        // https://www.khronos.org/opengles/sdk/docs/man3/html/glTexStorage2D.xhtml
+    case LOCAL_GL_R8:
+    case LOCAL_GL_R8_SNORM:
+    case LOCAL_GL_R16F:
+    case LOCAL_GL_R32F:
+    case LOCAL_GL_R8UI:
+    case LOCAL_GL_R8I:
+    case LOCAL_GL_R16UI:
+    case LOCAL_GL_R16I:
+    case LOCAL_GL_R32UI:
+    case LOCAL_GL_R32I:
+    case LOCAL_GL_RG8:
+    case LOCAL_GL_RG8_SNORM:
+    case LOCAL_GL_RG16F:
+    case LOCAL_GL_RG32F:
+    case LOCAL_GL_RG8UI:
+    case LOCAL_GL_RG8I:
+    case LOCAL_GL_RG16UI:
+    case LOCAL_GL_RG16I:
+    case LOCAL_GL_RG32UI:
+    case LOCAL_GL_RG32I:
+    case LOCAL_GL_RGB8:
+    case LOCAL_GL_SRGB8:
+    case LOCAL_GL_RGB565:
+    case LOCAL_GL_RGB8_SNORM:
+    case LOCAL_GL_R11F_G11F_B10F:
+    case LOCAL_GL_RGB9_E5:
+    case LOCAL_GL_RGB16F:
+    case LOCAL_GL_RGB32F:
+    case LOCAL_GL_RGB8UI:
+    case LOCAL_GL_RGB8I:
+    case LOCAL_GL_RGB16UI:
+    case LOCAL_GL_RGB16I:
+    case LOCAL_GL_RGB32UI:
+    case LOCAL_GL_RGB32I:
+    case LOCAL_GL_RGBA8:
+    case LOCAL_GL_SRGB8_ALPHA8:
+    case LOCAL_GL_RGBA8_SNORM:
+    case LOCAL_GL_RGB5_A1:
+    case LOCAL_GL_RGBA4:
+    case LOCAL_GL_RGB10_A2:
+    case LOCAL_GL_RGBA16F:
+    case LOCAL_GL_RGBA32F:
+    case LOCAL_GL_RGBA8UI:
+    case LOCAL_GL_RGBA8I:
+    case LOCAL_GL_RGB10_A2UI:
+    case LOCAL_GL_RGBA16UI:
+    case LOCAL_GL_RGBA16I:
+    case LOCAL_GL_RGBA32I:
+    case LOCAL_GL_RGBA32UI:
+    case LOCAL_GL_DEPTH_COMPONENT16:
+    case LOCAL_GL_DEPTH_COMPONENT24:
+    case LOCAL_GL_DEPTH_COMPONENT32F:
+    case LOCAL_GL_DEPTH24_STENCIL8:
+    case LOCAL_GL_DEPTH32F_STENCIL8:
+        return true;
+    }
+
+    if (IsCompressedTextureFormat(internalformat)) {
+        return true;
+    }
+
+    const char* name = EnumName(internalformat);
+    if (name && name[0] != '[')
+        ErrorInvalidEnum("%s: invalid internal format %s", info, name);
+    else
+        ErrorInvalidEnum("%s: invalid internal format 0x%04X", info, internalformat);
+
+    return false;
+}
+
+/** Validates parameters to texStorage{2D,3D} */
+bool
+WebGL2Context::ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat,
+                                      GLsizei width, GLsizei height, GLsizei depth,
+                                      const char* info)
+{
+    // GL_INVALID_OPERATION is generated if the default texture object is curently bound to target.
+    WebGLTexture* tex = activeBoundTextureForTarget(target);
+    if (!tex) {
+        ErrorInvalidOperation("%s: no texture is bound to target %s", info, EnumName(target));
+        return false;
+    }
+
+    // GL_INVALID_OPERATION is generated if the texture object currently bound to target already has
+    // GL_TEXTURE_IMMUTABLE_FORMAT set to GL_TRUE.
+    if (tex->IsImmutable()) {
+        ErrorInvalidOperation("%s: texture bound to target %s is already immutable", info, EnumName(target));
+        return false;
+    }
+
+    // GL_INVALID_ENUM is generated if internalformat is not a valid sized internal format.
+    if (!ValidateSizedInternalFormat(internalformat, info))
+        return false;
+
+    // GL_INVALID_VALUE is generated if width, height or levels are less than 1.
+    if (width < 1)  { ErrorInvalidValue("%s: width is < 1", info);  return false; }
+    if (height < 1) { ErrorInvalidValue("%s: height is < 1", info); return false; }
+    if (depth < 1)  { ErrorInvalidValue("%s: depth is < 1", info);  return false; }
+    if (levels < 1) { ErrorInvalidValue("%s: levels is < 1", info); return false; }
+
+    // The following check via FloorLog2 only requires a depth value if target is TEXTURE_3D.
+    bool is3D = (target != LOCAL_GL_TEXTURE_3D);
+    if (!is3D)
+        depth = 1;
+
+    // GL_INVALID_OPERATION is generated if levels is greater than floor(log2(max(width, height, depth)))+1.
+    if (FloorLog2(std::max(std::max(width, height), depth))+1 < levels) {
+        ErrorInvalidOperation("%s: levels > floor(log2(max(width, height%s)))+1", info, is3D ? ", depth" : "");
+        return false;
+    }
+
+    return true;
+}
+
 // -------------------------------------------------------------------------
 // Texture objects
 
 void
 WebGL2Context::TexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    // GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants.
+    if (target != LOCAL_GL_TEXTURE_2D && target != LOCAL_GL_TEXTURE_CUBE_MAP)
+        return ErrorInvalidEnum("texStorage2D: target is not TEXTURE_2D or TEXTURE_CUBE_MAP.");
+
+    if (!ValidateTexStorage(target, levels, internalformat, width, height, 1, "texStorage2D"))
+        return;
+
+    WebGLTexture* tex = activeBoundTextureForTarget(target);
+    tex->SetImmutable();
+
+    const size_t facesCount = (target == LOCAL_GL_TEXTURE_2D) ? 1 : 6;
+    GLsizei w = width;
+    GLsizei h = height;
+    for (size_t l = 0; l < size_t(levels); l++) {
+        for (size_t f = 0; f < facesCount; f++) {
+            TexImageTarget imageTarget = TexImageTargetForTargetAndFace(target, f);
+            // FIXME: SetImageInfo wants a type, to go with the internalformat that it stores.
+            // 'type' is deprecated by sized internalformats, which are how TexStorage works.
+            // We must fix WebGLTexture::ImageInfo to store an "effective internalformat",
+            // which in the present case is just the sized internalformat, and drop 'types'
+            // altogether. For now, we just pass LOCAL_GL_UNSIGNED_BYTE, which works For
+            // the most commonly used formats.
+            const GLenum type = LOCAL_GL_UNSIGNED_BYTE;
+            tex->SetImageInfo(imageTarget, l, w, h,
+                              internalformat, type,
+                              WebGLImageDataStatus::UninitializedImageData);
+        }
+        w = std::max(1, w/2);
+        h = std::max(1, h/2);
+    }
+
+    gl->fTexStorage2D(target, levels, internalformat, width, height);
 }
 
 void
 WebGL2Context::TexStorage3D(GLenum target, GLsizei levels, GLenum internalformat,
                             GLsizei width, GLsizei height, GLsizei depth)
 {
     MOZ_CRASH("Not Implemented.");
 }
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -390,16 +390,22 @@ WebGLContext::CopyTexSubImage2D_base(Tex
 
     MakeContextCurrent();
 
     WebGLTexture *tex = activeBoundTextureForTexImageTarget(texImageTarget);
 
     if (!tex)
         return ErrorInvalidOperation("%s: no texture is bound to this target");
 
+    if (tex->IsImmutable()) {
+        if (!sub) {
+            return ErrorInvalidOperation("copyTexImage2D: disallowed because the texture bound to this target has already been made immutable by texStorage2D");
+        }
+    }
+
     if (CanvasUtils::CheckSaneSubrectSize(x, y, width, height, framebufferWidth, framebufferHeight)) {
         if (sub)
             gl->fCopyTexSubImage2D(texImageTarget.get(), level, xoffset, yoffset, x, y, width, height);
         else
             gl->fCopyTexImage2D(texImageTarget.get(), level, internalformat, x, y, width, height, 0);
     } else {
 
         // the rect doesn't fit in the framebuffer
@@ -3331,20 +3337,27 @@ WebGLContext::CompressedTexImage2D(GLenu
 
     if (!ValidateCompTexImageSize(level, internalformat, 0, 0, width, height, width, height, func))
     {
         return;
     }
 
     const TexImageTarget texImageTarget(rawTexImgTarget);
 
+    WebGLTexture* tex = activeBoundTextureForTexImageTarget(texImageTarget);
+    MOZ_ASSERT(tex);
+    if (tex->IsImmutable()) {
+        return ErrorInvalidOperation(
+            "compressedTexImage2D: disallowed because the texture bound to "
+            "this target has already been made immutable by texStorage2D");
+    }
+
     MakeContextCurrent();
     gl->fCompressedTexImage2D(texImageTarget.get(), level, internalformat, width, height, border, byteLength, view.Data());
-    WebGLTexture* tex = activeBoundTextureForTexImageTarget(texImageTarget);
-    MOZ_ASSERT(tex);
+
     tex->SetImageInfo(texImageTarget, level, width, height, internalformat, LOCAL_GL_UNSIGNED_BYTE,
                       WebGLImageDataStatus::InitializedImageData);
 }
 
 void
 WebGLContext::CompressedTexSubImage2D(GLenum rawTexImgTarget, GLint level, GLint xoffset,
                                       GLint yoffset, GLsizei width, GLsizei height,
                                       GLenum internalformat,
@@ -3682,16 +3695,21 @@ WebGLContext::TexImage2D_base(TexImageTa
         return ErrorInvalidOperation("texImage2D: not enough data for operation (need %d, have %d)",
                                  bytesNeeded, byteLength);
 
     WebGLTexture *tex = activeBoundTextureForTexImageTarget(texImageTarget);
 
     if (!tex)
         return ErrorInvalidOperation("texImage2D: no texture is bound to this target");
 
+    if (tex->IsImmutable()) {
+        return ErrorInvalidOperation(
+            "texImage2D: disallowed because the texture "
+            "bound to this target has already been made immutable by texStorage2D");
+    }
     MakeContextCurrent();
 
     nsAutoArrayPtr<uint8_t> convertedData;
     void* pixels = nullptr;
     WebGLImageDataStatus imageInfoStatusIfSuccess = WebGLImageDataStatus::UninitializedImageData;
 
     if (byteLength) {
         size_t   srcStride = srcStrideOrZero ? srcStrideOrZero : checked_alignedRowSize.value();
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -501,16 +501,26 @@ WebGLContext::IsCompressedTextureFormat(
         case LOCAL_GL_ATC_RGB:
         case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
         case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
         case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1:
         case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1:
         case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1:
         case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
         case LOCAL_GL_ETC1_RGB8_OES:
+        case LOCAL_GL_COMPRESSED_R11_EAC:
+        case LOCAL_GL_COMPRESSED_SIGNED_R11_EAC:
+        case LOCAL_GL_COMPRESSED_RG11_EAC:
+        case LOCAL_GL_COMPRESSED_SIGNED_RG11_EAC:
+        case LOCAL_GL_COMPRESSED_RGB8_ETC2:
+        case LOCAL_GL_COMPRESSED_SRGB8_ETC2:
+        case LOCAL_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+        case LOCAL_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+        case LOCAL_GL_COMPRESSED_RGBA8_ETC2_EAC:
+        case LOCAL_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
             return true;
         default:
             return false;
     }
 }
 
 
 bool
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -27,16 +27,17 @@ WebGLTexture::WebGLTexture(WebGLContext 
     , WebGLContextBoundObject(context)
     , mMinFilter(LOCAL_GL_NEAREST_MIPMAP_LINEAR)
     , mMagFilter(LOCAL_GL_LINEAR)
     , mWrapS(LOCAL_GL_REPEAT)
     , mWrapT(LOCAL_GL_REPEAT)
     , mFacesCount(0)
     , mMaxLevelWithCustomImages(0)
     , mHaveGeneratedMipmap(false)
+    , mImmutable(false)
     , mFakeBlackStatus(WebGLTextureFakeBlackStatus::IncompleteTexture)
 {
     SetIsDOMBinding();
     mContext->MakeContextCurrent();
     mContext->gl->fGenTextures(1, &mGLName);
     mContext->mTextures.insertBack(this);
 }
 
@@ -222,29 +223,22 @@ WebGLTexture::IsCubeComplete() const {
     if (mTarget != LOCAL_GL_TEXTURE_CUBE_MAP)
         return false;
     const ImageInfo &first = ImageInfoAt(LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0);
     if (!first.IsPositive() || !first.IsSquare())
         return false;
     return AreAllLevel0ImageInfosEqual();
 }
 
-static TexImageTarget
-GLCubeMapFaceById(int id)
-{
-    // Correctness is checked by the constructor for TexImageTarget
-    return TexImageTarget(LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + id);
-}
-
 bool
 WebGLTexture::IsMipmapCubeComplete() const {
     if (!IsCubeComplete()) // in particular, this checks that this is a cube map
         return false;
     for (int i = 0; i < 6; i++) {
-        const TexImageTarget face = GLCubeMapFaceById(i);
+        const TexImageTarget face = TexImageTargetForTargetAndFace(LOCAL_GL_TEXTURE_CUBE_MAP, i);
         if (!DoesTexture2DMipmapHaveAllLevelsConsistentlyDefined(face))
             return false;
     }
     return true;
 }
 
 WebGLTextureFakeBlackStatus
 WebGLTexture::ResolvedFakeBlackStatus() {
@@ -404,19 +398,17 @@ WebGLTexture::ResolvedFakeBlackStatus() 
         if (hasAnyInitializedImageData) {
             // The texture contains some initialized image data, and some uninitialized image data.
             // In this case, we have no choice but to initialize all image data now. Fortunately,
             // in this case we know that we can't be dealing with a depth texture per WEBGL_depth_texture
             // and ANGLE_depth_texture (which allow only one image per texture) so we can assume that
             // glTexImage2D is able to upload data to images.
             for (size_t level = 0; level <= mMaxLevelWithCustomImages; ++level) {
                 for (size_t face = 0; face < mFacesCount; ++face) {
-                    TexImageTarget imageTarget = mTarget == LOCAL_GL_TEXTURE_2D
-                                                 ? LOCAL_GL_TEXTURE_2D
-                                                 : LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
+                    TexImageTarget imageTarget = TexImageTargetForTargetAndFace(mTarget, face);
                     const ImageInfo& imageInfo = ImageInfoAt(imageTarget, level);
                     if (imageInfo.mImageDataStatus == WebGLImageDataStatus::UninitializedImageData) {
                         DoDeferredImageInitialization(imageTarget, level);
                     }
                 }
             }
             mFakeBlackStatus = WebGLTextureFakeBlackStatus::NotNeeded;
         } else {
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -197,17 +197,19 @@ protected:
 
     TexMinFilter mMinFilter;
     TexMagFilter mMagFilter;
     TexWrap mWrapS, mWrapT;
 
     size_t mFacesCount, mMaxLevelWithCustomImages;
     nsTArray<ImageInfo> mImageInfos;
 
-    bool mHaveGeneratedMipmap;
+    bool mHaveGeneratedMipmap; // set by generateMipmap
+    bool mImmutable; // set by texStorage*
+
     WebGLTextureFakeBlackStatus mFakeBlackStatus;
 
     void EnsureMaxLevelWithCustomImagesAtLeast(size_t aMaxLevelWithCustomImages) {
         mMaxLevelWithCustomImages = std::max(mMaxLevelWithCustomImages, aMaxLevelWithCustomImages);
         mImageInfos.EnsureLengthAtLeast((mMaxLevelWithCustomImages + 1) * mFacesCount);
     }
 
     bool CheckFloatTextureFilterParams() const {
@@ -266,16 +268,29 @@ public:
     bool IsMipmapTexture2DComplete() const;
 
     bool IsCubeComplete() const;
 
     bool IsMipmapCubeComplete() const;
 
     void SetFakeBlackStatus(WebGLTextureFakeBlackStatus x);
 
+    bool IsImmutable() const { return mImmutable; }
+    void SetImmutable() { mImmutable = true; }
+
+    size_t MaxLevelWithCustomImages() const { return mMaxLevelWithCustomImages; }
+
     // Returns the current fake-black-status, except if it was Unknown,
     // in which case this function resolves it first, so it never returns Unknown.
     WebGLTextureFakeBlackStatus ResolvedFakeBlackStatus();
 };
 
+inline TexImageTarget
+TexImageTargetForTargetAndFace(TexTarget target, size_t face)
+{
+    return target == LOCAL_GL_TEXTURE_2D
+           ? LOCAL_GL_TEXTURE_2D
+           : LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
+}
+
 } // namespace mozilla
 
 #endif
--- a/dom/identity/Identity.manifest
+++ b/dom/identity/Identity.manifest
@@ -1,9 +1,8 @@
 # nsDOMIdentity.js
 component {210853d9-2c97-4669-9761-b1ab9cbf57ef} nsDOMIdentity.js
-contract @mozilla.org/dom/identity;1 {210853d9-2c97-4669-9761-b1ab9cbf57ef}
-category JavaScript-navigator-property mozId @mozilla.org/dom/identity;1
+contract @mozilla.org/identity/manager;1 {210853d9-2c97-4669-9761-b1ab9cbf57ef}
 
 # nsIDService.js (initialization on startup)
 component {4e0a0e98-b1d3-4745-a1eb-f815199dd06b} nsIDService.js
 contract @mozilla.org/dom/identity/service;1 {4e0a0e98-b1d3-4745-a1eb-f815199dd06b}
 category app-startup IDService @mozilla.org/dom/identity/service;1
--- a/dom/identity/nsDOMIdentity.js
+++ b/dom/identity/nsDOMIdentity.js
@@ -41,39 +41,20 @@ XPCOMUtils.defineLazyServiceGetter(this,
 
 const ERRORS = {
   "ERROR_INVALID_ASSERTION_AUDIENCE":
     "Assertion audience may not differ from origin",
   "ERROR_REQUEST_WHILE_NOT_HANDLING_USER_INPUT":
     "The request() method may only be invoked when handling user input",
 };
 
-function nsDOMIdentity(aIdentityInternal) {
-  this._identityInternal = aIdentityInternal;
+function nsDOMIdentity() {
 }
-nsDOMIdentity.prototype = {
-  __exposedProps__: {
-    // Relying Party (RP)
-    watch: 'r',
-    request: 'r',
-    logout: 'r',
-    get: 'r',
-    getVerifiedEmail: 'r',
 
-    // Provisioning
-    beginProvisioning: 'r',
-    genKeyPair: 'r',
-    registerCertificate: 'r',
-    raiseProvisioningFailure: 'r',
-
-    // Authentication
-    beginAuthentication: 'r',
-    completeAuthentication: 'r',
-    raiseAuthenticationFailure: 'r'
-  },
+nsDOMIdentity.prototype = {
 
   // require native events unless syntheticEventsOk is set
   get nativeEventsRequired() {
     if (Services.prefs.prefHasUserValue(PREF_SYNTHETIC_EVENTS_OK) &&
         (Services.prefs.getPrefType(PREF_SYNTHETIC_EVENTS_OK) ===
          Ci.nsIPrefBranch.PREF_BOOL)) {
       return !Services.prefs.getBoolPref(PREF_SYNTHETIC_EVENTS_OK);
     }
@@ -143,17 +124,17 @@ nsDOMIdentity.prototype = {
     this._rpWatcher.audience = message.audience;
 
     if (message.errors.length) {
       this.reportErrors(message);
       // We don't delete the rpWatcher object, because we don't want the
       // broken client to be able to call watch() any more.  It's broken.
       return;
     }
-    this._identityInternal._mm.sendAsyncMessage(
+    this._mm.sendAsyncMessage(
       "Identity:RP:Watch",
       message,
       null,
       this._window.document.nodePrincipal
     );
   },
 
   request: function nsDOMIdentity_request(aOptions = {}) {
@@ -219,17 +200,17 @@ nsDOMIdentity.prototype = {
         throw new Error("oncancel is not a function");
       } else {
         // Store optional cancel callback for later.
         this._onCancelRequestCallback = aOptions.oncancel;
       }
     }
 
     this._rpCalls++;
-    this._identityInternal._mm.sendAsyncMessage(
+    this._mm.sendAsyncMessage(
       "Identity:RP:Request",
       message,
       null,
       this._window.document.nodePrincipal
     );
   },
 
   logout: function nsDOMIdentity_logout() {
@@ -244,17 +225,17 @@ nsDOMIdentity.prototype = {
     let message = this.DOMIdentityMessage();
 
     // Report and fail hard on any errors.
     if (message.errors.length) {
       this.reportErrors(message);
       return;
     }
 
-    this._identityInternal._mm.sendAsyncMessage(
+    this._mm.sendAsyncMessage(
       "Identity:RP:Logout",
       message,
       null,
       this._window.document.nodePrincipal
     );
   },
 
   /*
@@ -332,17 +313,17 @@ nsDOMIdentity.prototype = {
     if (this._beginProvisioningCallback) {
       throw new Error("navigator.id.beginProvisioning already called.");
     }
     if (!aCallback || typeof(aCallback) !== "function") {
       throw new Error("beginProvisioning callback is required.");
     }
 
     this._beginProvisioningCallback = aCallback;
-    this._identityInternal._mm.sendAsyncMessage(
+    this._mm.sendAsyncMessage(
       "Identity:IDP:BeginProvisioning",
       this.DOMIdentityMessage(),
       null,
       this._window.document.nodePrincipal
     );
   },
 
   genKeyPair: function nsDOMIdentity_genKeyPair(aCallback) {
@@ -353,17 +334,17 @@ nsDOMIdentity.prototype = {
     if (this._genKeyPairCallback) {
       throw new Error("navigator.id.genKeyPair already called.");
     }
     if (!aCallback || typeof(aCallback) !== "function") {
       throw new Error("genKeyPair callback is required.");
     }
 
     this._genKeyPairCallback = aCallback;
-    this._identityInternal._mm.sendAsyncMessage(
+    this._mm.sendAsyncMessage(
       "Identity:IDP:GenKeyPair",
       this.DOMIdentityMessage(),
       null,
       this._window.document.nodePrincipal
     );
   },
 
   registerCertificate: function nsDOMIdentity_registerCertificate(aCertificate) {
@@ -373,17 +354,17 @@ nsDOMIdentity.prototype = {
     }
     if (this._provisioningEnded) {
       throw new Error("Provisioning already ended");
     }
     this._provisioningEnded = true;
 
     let message = this.DOMIdentityMessage();
     message.cert = aCertificate;
-    this._identityInternal._mm.sendAsyncMessage(
+    this._mm.sendAsyncMessage(
       "Identity:IDP:RegisterCertificate",
       message,
       null,
       this._window.document.nodePrincipal
     );
   },
 
   raiseProvisioningFailure: function nsDOMIdentity_raiseProvisioningFailure(aReason) {
@@ -393,17 +374,17 @@ nsDOMIdentity.prototype = {
     }
     if (!aReason || typeof(aReason) != "string") {
       throw new Error("raiseProvisioningFailure reason is required");
     }
     this._provisioningEnded = true;
 
     let message = this.DOMIdentityMessage();
     message.reason = aReason;
-    this._identityInternal._mm.sendAsyncMessage(
+    this._mm.sendAsyncMessage(
       "Identity:IDP:ProvisioningFailure",
       message,
       null,
       this._window.document.nodePrincipal
     );
   },
 
   /**
@@ -418,34 +399,34 @@ nsDOMIdentity.prototype = {
     if (typeof(aCallback) !== "function") {
       throw new Error("beginAuthentication callback is required.");
     }
     if (!aCallback || typeof(aCallback) !== "function") {
       throw new Error("beginAuthentication callback is required.");
     }
 
     this._beginAuthenticationCallback = aCallback;
-    this._identityInternal._mm.sendAsyncMessage(
+    this._mm.sendAsyncMessage(
       "Identity:IDP:BeginAuthentication",
       this.DOMIdentityMessage(),
       null,
       this._window.document.nodePrincipal
     );
   },
 
   completeAuthentication: function nsDOMIdentity_completeAuthentication() {
     if (this._authenticationEnded) {
       throw new Error("Authentication already ended");
     }
     if (!this._beginAuthenticationCallback) {
       throw new Error("navigator.id.completeAuthentication called outside of authentication");
     }
     this._authenticationEnded = true;
 
-    this._identityInternal._mm.sendAsyncMessage(
+    this._mm.sendAsyncMessage(
       "Identity:IDP:CompleteAuthentication",
       this.DOMIdentityMessage(),
       null,
       this._window.document.nodePrincipal
     );
   },
 
   raiseAuthenticationFailure: function nsDOMIdentity_raiseAuthenticationFailure(aReason) {
@@ -453,44 +434,24 @@ nsDOMIdentity.prototype = {
       throw new Error("Authentication already ended");
     }
     if (!aReason || typeof(aReason) != "string") {
       throw new Error("raiseProvisioningFailure reason is required");
     }
 
     let message = this.DOMIdentityMessage();
     message.reason = aReason;
-    this._identityInternal._mm.sendAsyncMessage(
+    this._mm.sendAsyncMessage(
       "Identity:IDP:AuthenticationFailure",
       message,
       null,
       this._window.document.nodePrincipal
     );
   },
 
-  // Private.
-  _init: function nsDOMIdentity__init(aWindow) {
-
-    this._initializeState();
-
-    // Store window and origin URI.
-    this._window = aWindow;
-    this._origin = aWindow.document.nodePrincipal.origin;
-    this._appStatus = aWindow.document.nodePrincipal.appStatus;
-    this._appId = aWindow.document.nodePrincipal.appId;
-
-    // Setup identifiers for current window.
-    let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIDOMWindowUtils);
-
-    // We need to inherit the id from the internalIdentity service.
-    // See comments below in that service's init.
-    this._id = this._identityInternal._id;
-  },
-
   /**
    * Called during init and shutdown.
    */
   _initializeState: function nsDOMIdentity__initializeState() {
     // Some state to prevent abuse
     // Limit the number of calls to .request
     this._rpCalls = 0;
     this._provisioningEnded = false;
@@ -498,22 +459,28 @@ nsDOMIdentity.prototype = {
 
     this._rpWatcher = null;
     this._onCancelRequestCallback = null;
     this._beginProvisioningCallback = null;
     this._genKeyPairCallback = null;
     this._beginAuthenticationCallback = null;
   },
 
-  _receiveMessage: function nsDOMIdentity_receiveMessage(aMessage) {
+  // nsIMessageListener
+  receiveMessage: function nsDOMIdentity_receiveMessage(aMessage) {
     let msg = aMessage.json;
 
+    // Is this message intended for this window?
+    if (msg.id != this._id) {
+      return;
+    }
+
     switch (aMessage.name) {
       case "Identity:ResetState":
-        if (!this._identityInternal._debug) {
+        if (!this._debug) {
           return;
         }
         this._initializeState();
         Services.obs.notifyObservers(null, "identity-DOM-state-reset", this._id);
         break;
       case "Identity:RP:Watch:OnLogin":
         // Do we have a watcher?
         if (!this._rpWatcher) {
@@ -581,20 +548,16 @@ nsDOMIdentity.prototype = {
         this._callGenKeyPairCallback(msg);
         break;
       case "Identity:IDP:CallBeginAuthenticationCallback":
         this._callBeginAuthenticationCallback(msg);
         break;
     }
   },
 
-  _log: function nsDOMIdentity__log(msg) {
-    this._identityInternal._log(msg);
-  },
-
   _callGenKeyPairCallback: function nsDOMIdentity__callGenKeyPairCallback(message) {
     // create a pubkey object that works
     let chrome_pubkey = JSON.parse(message.publicKey);
 
     // bunch of stuff to create a proper object in window context
     function genPropDesc(value) {
       return {
         enumerable: true, configurable: true, writable: true, value: value
@@ -671,98 +634,79 @@ nsDOMIdentity.prototype = {
     // Replace any audience supplied by the RP with one that has been sanitised
     message.audience = _audience;
 
     this._log("DOMIdentityMessage: " + JSON.stringify(message));
 
     return message;
   },
 
-  uninit: function DOMIdentity_uninit() {
-    this._log("nsDOMIdentity uninit() " + this._id);
-    this._identityInternal._mm.sendAsyncMessage(
-      "Identity:RP:Unwatch",
-      { id: this._id },
-      null,
-      this._window.document.nodePrincipal
-    );
-  }
-
-};
-
-/**
- * Internal functions that shouldn't be exposed to content.
- */
-function nsDOMIdentityInternal() {
-}
-nsDOMIdentityInternal.prototype = {
-
-  // nsIMessageListener
-  receiveMessage: function nsDOMIdentityInternal_receiveMessage(aMessage) {
-    let msg = aMessage.json;
-    // Is this message intended for this window?
-    if (msg.id != this._id) {
-      return;
-    }
-    this._identity._receiveMessage(aMessage);
-  },
-
+ /**
+  * Internal methods that are not exposed to content.
+  * See dom/webidl/Identity.webidl for the public interface.
+  */
   // nsIObserver
   observe: function nsDOMIdentityInternal_observe(aSubject, aTopic, aData) {
     let window = aSubject.QueryInterface(Ci.nsIDOMWindow);
     if (window != this._window) {
       return;
     }
 
-    this._identity.uninit();
+    this.uninit();
 
     Services.obs.removeObserver(this, "dom-window-destroyed");
-    this._identity._initializeState();
-    this._identity = null;
+    this._initializeState();
 
     // TODO: Also send message to DOMIdentity notifiying window is no longer valid
     // ie. in the case that the user closes the auth. window and we need to know.
 
     try {
       for (let msgName of this._messages) {
         this._mm.removeMessageListener(msgName, this);
       }
     } catch (ex) {
       // Avoid errors when removing more than once.
     }
 
     this._mm = null;
   },
 
-  // nsIDOMGlobalPropertyInitializer
+  //  Because we implement nsIDOMGlobalPropertyInitializer, our init() method
+  //  is invoked with content window as its single argument.
   init: function nsDOMIdentityInternal_init(aWindow) {
     if (Services.prefs.getPrefType(PREF_ENABLED) != Ci.nsIPrefBranch.PREF_BOOL
         || !Services.prefs.getBoolPref(PREF_ENABLED)) {
       return null;
     }
 
     this._debug =
       Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
       && Services.prefs.getBoolPref(PREF_DEBUG);
 
+    // Setup identifiers for current window.
     let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                       .getInterface(Ci.nsIDOMWindowUtils);
 
     // To avoid cross-process windowId collisions, use a uuid as an
     // almost certainly unique identifier.
     //
     // XXX Bug 869182 - use a combination of child process id and
     // innerwindow id to construct the unique id.
     this._id = uuidgen.generateUUID().toString();
     this._window = aWindow;
 
     // nsDOMIdentity needs to know our _id, so this goes after
     // its creation.
-    this._identity = new nsDOMIdentity(this);
-    this._identity._init(aWindow);
+    this._initializeState();
+
+    // Store window and origin URI.
+    this._window = aWindow;
+    this._origin = aWindow.document.nodePrincipal.origin;
+    this._appStatus = aWindow.document.nodePrincipal.appStatus;
+    this._appId = aWindow.document.nodePrincipal.appId;
 
     this._log("init was called from " + aWindow.document.location);
 
     this._mm = cpmm;
 
     // Setup listeners for messages from parent process.
     this._messages = [
       "Identity:ResetState",
@@ -776,34 +720,42 @@ nsDOMIdentityInternal.prototype = {
       "Identity:IDP:CallBeginAuthenticationCallback"
     ];
     this._messages.forEach(function(msgName) {
       this._mm.addMessageListener(msgName, this);
     }, this);
 
     // Setup observers so we can remove message listeners.
     Services.obs.addObserver(this, "dom-window-destroyed", false);
+  },
 
-    return this._identity;
-  },
+  uninit: function DOMIdentity_uninit() {
+    this._log("nsDOMIdentity uninit() " + this._id);
+    this._mm.sendAsyncMessage(
+      "Identity:RP:Unwatch",
+      { id: this._id }
+    );
+   },
 
   // Private.
   _log: function nsDOMIdentityInternal__log(msg) {
     if (!this._debug) {
       return;
     }
     dump("nsDOMIdentity (" + this._id + "): " + msg + "\n");
   },
 
   // Component setup.
   classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"),
 
-  QueryInterface: XPCOMUtils.generateQI(
-    [Ci.nsIDOMGlobalPropertyInitializer, Ci.nsIMessageListener]
-  ),
+  QueryInterface: XPCOMUtils.generateQI([
+      Ci.nsIMessageListener,
+      Ci.nsIObserver,
+      Ci.nsIDOMGlobalPropertyInitializer
+  ]),
 
   classInfo: XPCOMUtils.generateCI({
     classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"),
     contractID: "@mozilla.org/dom/identity;1",
     interfaces: [],
     classDescription: "Identity DOM Implementation"
   })
 };
@@ -835,9 +787,9 @@ function assertCorrectCallbacks(aOptions
 
   for (let cbName of optionalCallbacks) {
     if (aOptions[cbName] && typeof(aOptions[cbName]) != "function") {
       throw new Error(cbName + " must be a function");
     }
   }
 }
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDOMIdentityInternal]);
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDOMIdentity]);
--- a/dom/tests/mochitest/geolocation/test_mozsettings.html
+++ b/dom/tests/mochitest/geolocation/test_mozsettings.html
@@ -9,23 +9,25 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="geolocation_common.js"></script>
 
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=777594">Mozilla Bug 777594</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
+var timeToWaitMs = 1000;
+
 resume_geolocationProvider(function() {
   force_prompt(true, test1);
 });
 
 if (SpecialPowers.isMainProcess()) {
   SpecialPowers.Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
 }
 
@@ -41,41 +43,41 @@ function test1() {
 function test2() {
   ok(navigator.geolocation, "get geolocation object");
 
   toggleGeolocationSetting(false, function() {
       ok(true, "turned off geolocation via mozSettings");
       setTimeout(function() {
 	  navigator.geolocation.getCurrentPosition(successCallbackAfterMozsettingOff,
                                                    failureCallbackAfterMozsettingOff);
-        }, 500); // need to wait a bit for all of these async callbacks to finish
+        }, timeToWaitMs); // need to wait a bit for all of these async callbacks to finish
   });
 }
 
 function successCallbackAfterMozsettingOff(position) {
   ok(false, "Success callback should not have been called after setting geolocation.enabled to false.");
 
   toggleGeolocationSetting(true, function() {
       ok(true, "turned on geolocation via mozSettings");
       setTimeout(function() {
          navigator.geolocation.getCurrentPosition(successCallbackAfterMozsettingOn,
                                                   failureCallbackAfterMozsettingOn);
-        }, 500); // need to wait a bit for all of these async callbacks to finish
+        }, timeToWaitMs); // need to wait a bit for all of these async callbacks to finish
     });
 }
 
 function failureCallbackAfterMozsettingOff(error) {
   ok(true, "Geolocation didn't work after setting geolocation.enabled to false.");
 
   toggleGeolocationSetting(true, function() {
       ok(true, "turned on geolocation via mozSettings");
       setTimeout(function() {
          navigator.geolocation.getCurrentPosition(successCallbackAfterMozsettingOn,
                                                   failureCallbackAfterMozsettingOn);
-        }, 500); // need to wait a bit for all of these async callbacks to finish
+        }, timeToWaitMs); // need to wait a bit for all of these async callbacks to finish
     });
 }
 
 function successCallbackAfterMozsettingOn(position) {
   ok(true, "Geolocation worked after setting geolocation.enabled to true.");
   SimpleTest.finish();
 }
 
new file mode 100644
--- /dev/null
+++ b/dom/webidl/Identity.webidl
@@ -0,0 +1,70 @@
+/* -*- Mode: IDL; 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/.
+ */
+
+callback IdentityOnReadyCallback = void();
+callback IdentityOnLoginCallback = void(DOMString identityAssertion);
+callback IdentityOnLogoutCallback = void();
+callback IdentityOnCancelCallback = void(DOMString? error);
+callback IdentityOnErrorCallback = void(DOMString error);
+
+dictionary IdentityWatchOptions {
+  // Required callback
+  IdentityOnLoginCallback onlogin;
+
+  // Optional parameters
+  DOMString wantIssuer;
+  DOMString loggedInUser;
+
+  // Optional callbacks
+  IdentityOnReadyCallback onready;
+  IdentityOnLogoutCallback onlogout;
+  IdentityOnErrorCallback onerror;
+
+  // Certified apps can specify this
+  DOMString audience;
+};
+
+dictionary IdentityRequestOptions {
+  // Optional parameters
+  long refreshAuthentication;
+  DOMString termsOfService;
+  DOMString privacyPolicy;
+  DOMString backgroundColor;
+  DOMString siteLogo;
+  DOMString siteName;
+  DOMString returnTo;
+
+  IdentityOnCancelCallback oncancel;
+
+  // Certified apps can specify this
+  DOMString origin;
+};
+
+dictionary IdentityGetOptions {
+  DOMString privacyPolicy;
+  DOMString termsOfService;
+  DOMString privacyURL;
+  DOMString tosURL;
+  DOMString siteName;
+  DOMString siteLogo;
+};
+
+[JSImplementation="@mozilla.org/identity/manager;1",
+ NoInterfaceObject,
+ NavigatorProperty="mozId",
+ Pref="dom.identity.enabled"]
+interface IdentityManager {
+  void watch(optional IdentityWatchOptions options);
+  void request(optional IdentityRequestOptions options);
+  void logout();
+
+  [Pref="dom.identity.exposeLegacyGetAPI"]
+  void get(IdentityOnLoginCallback callback, optional IdentityGetOptions options);
+
+  [Pref="dom.identity.exposeLegacyGetVerifiedEmailAPI"]
+  void getVerifiedEmail(IdentityOnLoginCallback callback);
+};
+
--- a/dom/webidl/SpeechGrammarList.webidl
+++ b/dom/webidl/SpeechGrammarList.webidl
@@ -5,17 +5,17 @@
  *
  * The origin of this IDL file is
  * http://dvcs.w3.org/hg/speech-api/raw-file/tip/speechapi.html
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
-[Pref="media.webspeech.recognition.enable"]
+[Constructor, Pref="media.webspeech.recognition.enable"]
 interface SpeechGrammarList {
     readonly attribute unsigned long length;
     [Throws]
     getter SpeechGrammar item(unsigned long index);
     [Throws]
     void addFromURI(DOMString src, optional float weight);
     [Throws]
     void addFromString(DOMString string, optional float weight);
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -227,16 +227,17 @@ WEBIDL_FILES = [
     'IDBIndex.webidl',
     'IDBKeyRange.webidl',
     'IDBMutableFile.webidl',
     'IDBObjectStore.webidl',
     'IDBOpenDBRequest.webidl',
     'IDBRequest.webidl',
     'IDBTransaction.webidl',
     'IDBVersionChangeEvent.webidl',
+    'Identity.webidl',
     'ImageCapture.webidl',
     'ImageData.webidl',
     'ImageDocument.webidl',
     'InputEvent.webidl',
     'InputMethod.webidl',
     'InspectorUtils.webidl',
     'InstallEvent.webidl',
     'InstallPhaseEvent.webidl',
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -121,16 +121,17 @@ static const char *sExtensionNames[] = {
     "GL_EXT_sRGB",
     "GL_EXT_shader_texture_lod",
     "GL_EXT_texture3D",
     "GL_EXT_texture_compression_dxt1",
     "GL_EXT_texture_compression_s3tc",
     "GL_EXT_texture_filter_anisotropic",
     "GL_EXT_texture_format_BGRA8888",
     "GL_EXT_texture_sRGB",
+    "GL_EXT_texture_storage",
     "GL_EXT_transform_feedback",
     "GL_EXT_unpack_subimage",
     "GL_IMG_read_format",
     "GL_IMG_texture_compression_pvrtc",
     "GL_IMG_texture_npot",
     "GL_KHR_debug",
     "GL_NV_draw_instanced",
     "GL_NV_fence",
@@ -904,16 +905,39 @@ GLContext::InitWithPrefix(const char *pr
             if (!LoadSymbols(useCore ? coreSymbols : extSymbols, trygl, prefix)) {
                 NS_ERROR("GL supports array instanced without supplying it function.");
 
                 MarkUnsupported(GLFeature::instanced_arrays);
                 ClearSymbols(coreSymbols);
             }
         }
 
+        if (IsSupported(GLFeature::texture_storage)) {
+            SymLoadStruct coreSymbols[] = {
+                { (PRFuncPtr*) &mSymbols.fTexStorage2D, { "TexStorage2D", nullptr } },
+                { (PRFuncPtr*) &mSymbols.fTexStorage3D, { "TexStorage3D", nullptr } },
+                END_SYMBOLS
+            };
+
+            SymLoadStruct extSymbols[] = {
+                { (PRFuncPtr*) &mSymbols.fTexStorage2D, { "TexStorage2DEXT", nullptr } },
+                { (PRFuncPtr*) &mSymbols.fTexStorage3D, { "TexStorage3DEXT", nullptr } },
+                END_SYMBOLS
+            };
+
+            bool useCore = IsFeatureProvidedByCoreSymbols(GLFeature::texture_storage);
+            if (!LoadSymbols(useCore ? coreSymbols : extSymbols, trygl, prefix)) {
+                NS_ERROR("GL supports texture storage without supplying its functions.");
+
+                MarkUnsupported(GLFeature::texture_storage);
+                MarkExtensionSupported(useCore ? ARB_texture_storage : EXT_texture_storage);
+                ClearSymbols(coreSymbols);
+            }
+        }
+
         if (IsSupported(GLFeature::sampler_objects)) {
             SymLoadStruct samplerObjectsSymbols[] = {
                 { (PRFuncPtr*) &mSymbols.fGenSamplers, { "GenSamplers", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fDeleteSamplers, { "DeleteSamplers", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fIsSampler, { "IsSampler", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fBindSampler, { "BindSampler", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fSamplerParameteri, { "SamplerParameteri", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fSamplerParameteriv, { "SamplerParameteriv", nullptr } },
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -403,16 +403,17 @@ public:
         EXT_sRGB,
         EXT_shader_texture_lod,
         EXT_texture3D,
         EXT_texture_compression_dxt1,
         EXT_texture_compression_s3tc,
         EXT_texture_filter_anisotropic,
         EXT_texture_format_BGRA8888,
         EXT_texture_sRGB,
+        EXT_texture_storage,
         EXT_transform_feedback,
         EXT_unpack_subimage,
         IMG_read_format,
         IMG_texture_compression_pvrtc,
         IMG_texture_npot,
         KHR_debug,
         NV_draw_instanced,
         NV_fence,
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -2454,16 +2454,17 @@ bool AsyncPanZoomController::UpdateAnima
     }
     UpdateSharedCompositorFrameMetrics();
     return true;
   }
   return false;
 }
 
 Matrix4x4 AsyncPanZoomController::GetOverscrollTransform() const {
+  ReentrantMonitorAutoEnter lock(mMonitor);
   if (!IsOverscrolled()) {
     return Matrix4x4();
   }
 
   // The overscroll effect is a uniform stretch along the overscrolled axis,
   // with the edge of the content where we have reached the end of the
   // scrollable area pinned into place.
 
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -55,16 +55,19 @@
 #include "mozilla/layers/CompositorD3D9.h"
 #endif
 #include "GeckoProfiler.h"
 #include "mozilla/ipc/ProtocolTypes.h"
 #include "mozilla/unused.h"
 #include "mozilla/Hal.h"
 #include "mozilla/HalTypes.h"
 #include "mozilla/StaticPtr.h"
+#ifdef MOZ_ENABLE_PROFILER_SPS
+#include "ProfilerMarkers.h"
+#endif
 
 namespace mozilla {
 namespace layers {
 
 using namespace base;
 using namespace mozilla::ipc;
 using namespace mozilla::gfx;
 using namespace std;
@@ -1112,16 +1115,35 @@ CompositorParent::ComputeRenderIntegrity
 {
   if (mLayerManager) {
     return mLayerManager->ComputeRenderIntegrity();
   }
 
   return 1.0f;
 }
 
+static void
+InsertVsyncProfilerMarker(TimeStamp aVsyncTimestamp)
+{
+#ifdef MOZ_ENABLE_PROFILER_SPS
+  MOZ_ASSERT(CompositorParent::IsInCompositorThread());
+  MOZ_ASSERT(profiler_is_active());
+  VsyncPayload* payload = new VsyncPayload(aVsyncTimestamp);
+  PROFILER_MARKER_PAYLOAD("VsyncTimestamp", payload);
+#endif
+}
+
+/*static */ void
+CompositorParent::PostInsertVsyncProfilerMarker(TimeStamp aVsyncTimestamp)
+{
+  if (profiler_is_active()) {
+    CompositorLoop()->PostTask(FROM_HERE,
+      NewRunnableFunction(InsertVsyncProfilerMarker, aVsyncTimestamp));
+  }
+}
 
 /**
  * This class handles layer updates pushed directly from child
  * processes to the compositor thread.  It's associated with a
  * CompositorParent on the compositor thread.  While it uses the
  * PCompositor protocol to manage these updates, it doesn't actually
  * drive compositing itself.  For that it hands off work to the
  * CompositorParent it's associated with.
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -256,16 +256,21 @@ public:
 
   /**
    * Lookup the indirect shadow tree for |aId| and return it if it
    * exists.  Otherwise null is returned.  This must only be called on
    * the compositor thread.
    */
   static LayerTreeState* GetIndirectShadowTree(uint64_t aId);
 
+  /**
+   * Used by the profiler to denote when a vsync occured
+   */
+  static void PostInsertVsyncProfilerMarker(mozilla::TimeStamp aVsyncTimestamp);
+
   float ComputeRenderIntegrity();
 
   /**
    * Returns true if the calling thread is the compositor thread.
    */
   static bool IsInCompositorThread();
 
 protected:
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -198,17 +198,17 @@ LayerTransactionParent::RecvUpdate(const
                                    const TargetConfig& targetConfig,
                                    const bool& isFirstPaint,
                                    const bool& scheduleComposite,
                                    const uint32_t& paintSequenceNumber,
                                    const bool& isRepeatTransaction,
                                    const mozilla::TimeStamp& aTransactionStart,
                                    InfallibleTArray<EditReply>* reply)
 {
-  profiler_tracing("Paint", "Composite", TRACING_INTERVAL_START);
+  profiler_tracing("Paint", "LayerTransaction", TRACING_INTERVAL_START);
   PROFILER_LABEL("LayerTransactionParent", "RecvUpdate",
     js::ProfileEntry::Category::GRAPHICS);
 
 #ifdef COMPOSITOR_PERFORMANCE_WARNING
   TimeStamp updateStart = TimeStamp::Now();
 #endif
 
   MOZ_LAYERS_LOG(("[ParentSide] received txn with %d edits", cset.Length()));
@@ -576,16 +576,17 @@ LayerTransactionParent::RecvUpdate(const
     float severity = (latency - TimeDuration::FromMilliseconds(kVisualWarningTrigger)).ToMilliseconds() /
                        (kVisualWarningMax - kVisualWarningTrigger);
     if (severity > 1.f) {
       severity = 1.f;
     }
     mLayerManager->VisualFrameWarning(severity);
   }
 
+  profiler_tracing("Paint", "LayerTransaction", TRACING_INTERVAL_END);
   return true;
 }
 
 bool
 LayerTransactionParent::RecvSetTestSampleTime(const TimeStamp& aTime)
 {
   return mShadowLayersManager->SetTestSampleTime(this, aTime);
 }
--- a/hal/gonk/GonkHal.cpp
+++ b/hal/gonk/GonkHal.cpp
@@ -1479,16 +1479,64 @@ SetNiceForPid(int aPid, int aNice)
   }
 
   HAL_LOG("Changed nice for pid %d from %d to %d.",
           aPid, origProcPriority, aNice);
 
   closedir(tasksDir);
 }
 
+/*
+ * Used to store the nice value adjustments and oom_adj values for the various
+ * process priority levels.
+ */
+struct ProcessPriorityPrefs {
+  bool initialized;
+  int lowPriorityNice;
+  struct {
+    int nice;
+    int oomScoreAdj;
+  } priorities[NUM_PROCESS_PRIORITY];
+};
+
+/*
+ * Reads the preferences for the various process priority levels and sets up
+ * watchers so that if they're dynamically changed the change is reflected on
+ * the appropriate variables.
+ */
+void
+EnsureProcessPriorityPrefs(ProcessPriorityPrefs* prefs)
+{
+  if (prefs->initialized) {
+    return;
+  }
+
+  // Read the preferences for process priority levels
+  for (int i = PROCESS_PRIORITY_BACKGROUND; i < NUM_PROCESS_PRIORITY; i++) {
+    ProcessPriority priority = static_cast<ProcessPriority>(i);
+
+    // Read the nice values
+    const char* processPriorityStr = ProcessPriorityToString(priority);
+    nsPrintfCString niceStr("hal.processPriorityManager.gonk.%s.Nice",
+                            processPriorityStr);
+    Preferences::AddIntVarCache(&prefs->priorities[i].nice, niceStr.get());
+
+    // Read the oom_adj scores
+    nsPrintfCString oomStr("hal.processPriorityManager.gonk.%s.OomScoreAdjust",
+                           processPriorityStr);
+    Preferences::AddIntVarCache(&prefs->priorities[i].oomScoreAdj,
+                                oomStr.get());
+  }
+
+  Preferences::AddIntVarCache(&prefs->lowPriorityNice,
+                              "hal.processPriorityManager.gonk.LowCPUNice");
+
+  prefs->initialized = true;
+}
+
 void
 SetProcessPriority(int aPid,
                    ProcessPriority aPriority,
                    ProcessCPUPriority aCPUPriority,
                    uint32_t aBackgroundLRU)
 {
   HAL_LOG("SetProcessPriority(pid=%d, priority=%d, cpuPriority=%d, LRU=%u)",
           aPid, aPriority, aCPUPriority, aBackgroundLRU);
@@ -1497,72 +1545,59 @@ SetProcessPriority(int aPid,
   // OOM parameters according to our prefs.
   //
   // We could/should do this on startup instead of waiting for the first
   // SetProcessPriorityCall.  But in practice, the master process needs to set
   // its priority early in the game, so we can reasonably rely on
   // SetProcessPriority being called early in startup.
   EnsureKernelLowMemKillerParamsSet();
 
-  int32_t oomScoreAdj = 0;
-  nsresult rv = Preferences::GetInt(nsPrintfCString(
-    "hal.processPriorityManager.gonk.%s.OomScoreAdjust",
-    ProcessPriorityToString(aPriority)).get(), &oomScoreAdj);
+  static ProcessPriorityPrefs prefs = { 0 };
+  EnsureProcessPriorityPrefs(&prefs);
+
+  int oomScoreAdj = prefs.priorities[aPriority].oomScoreAdj;
 
   RoundOomScoreAdjUpWithBackroundLRU(oomScoreAdj, aBackgroundLRU);
 
-  if (NS_SUCCEEDED(rv)) {
-    int clampedOomScoreAdj = clamped<int>(oomScoreAdj, OOM_SCORE_ADJ_MIN,
-                                                       OOM_SCORE_ADJ_MAX);
-    if(clampedOomScoreAdj != oomScoreAdj) {
-      HAL_LOG("Clamping OOM adjustment for pid %d to %d", aPid,
-              clampedOomScoreAdj);
-    } else {
-      HAL_LOG("Setting OOM adjustment for pid %d to %d", aPid,
-              clampedOomScoreAdj);
-    }
-
-    // We try the newer interface first, and fall back to the older interface
-    // on failure.
-
-    if (!WriteToFile(nsPrintfCString("/proc/%d/oom_score_adj", aPid).get(),
-                     nsPrintfCString("%d", clampedOomScoreAdj).get()))
-    {
-      int oomAdj = OomAdjOfOomScoreAdj(clampedOomScoreAdj);
-
-      WriteToFile(nsPrintfCString("/proc/%d/oom_adj", aPid).get(),
-                  nsPrintfCString("%d", oomAdj).get());
-    }
+  int clampedOomScoreAdj = clamped<int>(oomScoreAdj, OOM_SCORE_ADJ_MIN,
+                                                     OOM_SCORE_ADJ_MAX);
+  if (clampedOomScoreAdj != oomScoreAdj) {
+    HAL_LOG("Clamping OOM adjustment for pid %d to %d", aPid,
+            clampedOomScoreAdj);
   } else {
-    HAL_ERR("Unable to read oom_score_adj pref for priority %s; "
-            "are the prefs messed up?", ProcessPriorityToString(aPriority));
-    MOZ_ASSERT(false);
+    HAL_LOG("Setting OOM adjustment for pid %d to %d", aPid,
+            clampedOomScoreAdj);
   }
 
-  int32_t nice = 0;
+  // We try the newer interface first, and fall back to the older interface
+  // on failure.
+
+  if (!WriteToFile(nsPrintfCString("/proc/%d/oom_score_adj", aPid).get(),
+                   nsPrintfCString("%d", clampedOomScoreAdj).get()))
+  {
+    int oomAdj = OomAdjOfOomScoreAdj(clampedOomScoreAdj);
+
+    WriteToFile(nsPrintfCString("/proc/%d/oom_adj", aPid).get(),
+                nsPrintfCString("%d", oomAdj).get());
+  }
+
+  int nice = 0;
 
   if (aCPUPriority == PROCESS_CPU_PRIORITY_NORMAL) {
-    rv = Preferences::GetInt(
-      nsPrintfCString("hal.processPriorityManager.gonk.%s.Nice",
-                      ProcessPriorityToString(aPriority)).get(),
-      &nice);
+    nice = prefs.priorities[aPriority].nice;
   } else if (aCPUPriority == PROCESS_CPU_PRIORITY_LOW) {
-    rv = Preferences::GetInt("hal.processPriorityManager.gonk.LowCPUNice",
-                             &nice);
+    nice = prefs.lowPriorityNice;
   } else {
-    HAL_ERR("Unable to read niceness pref for priority %s; "
-            "are the prefs messed up?", ProcessPriorityToString(aPriority));
+    HAL_ERR("Unknown aCPUPriority value %d", aCPUPriority);
     MOZ_ASSERT(false);
-    rv = NS_ERROR_FAILURE;
+    return;
   }
 
-  if (NS_SUCCEEDED(rv)) {
-    HAL_LOG("Setting nice for pid %d to %d", aPid, nice);
-    SetNiceForPid(aPid, nice);
-  }
+  HAL_LOG("Setting nice for pid %d to %d", aPid, nice);
+  SetNiceForPid(aPid, nice);
 }
 
 static bool
 IsValidRealTimePriority(int aValue, int aSchedulePolicy)
 {
   return (aValue >= sched_get_priority_min(aSchedulePolicy)) &&
          (aValue <= sched_get_priority_max(aSchedulePolicy));
 }
@@ -1603,47 +1638,86 @@ SetRealTimeThreadPriority(pid_t aTid,
   int rv = sched_setscheduler(aTid, policy, &schedParam);
 
   if (rv) {
     HAL_LOG("Failed to set thread %d to real time priority level %s; error %s",
             aTid, ThreadPriorityToString(aThreadPriority), strerror(errno));
   }
 }
 
+/*
+ * Used to store the nice value adjustments and real time priorities for the
+ * various thread priority levels.
+ */
+struct ThreadPriorityPrefs {
+  bool initialized;
+  struct {
+    int nice;
+    int realTime;
+  } priorities[NUM_THREAD_PRIORITY];
+};
+
+/*
+ * Reads the preferences for the various process priority levels and sets up
+ * watchers so that if they're dynamically changed the change is reflected on
+ * the appropriate variables.
+ */
+void
+EnsureThreadPriorityPrefs(ThreadPriorityPrefs* prefs)
+{
+  if (prefs->initialized) {
+    return;
+  }
+
+  for (int i = THREAD_PRIORITY_COMPOSITOR; i < NUM_THREAD_PRIORITY; i++) {
+    ThreadPriority priority = static_cast<ThreadPriority>(i);
+
+    // Read the nice values
+    const char* threadPriorityStr = ThreadPriorityToString(priority);
+    nsPrintfCString niceStr("hal.gonk.%s.nice", threadPriorityStr);
+    Preferences::AddIntVarCache(&prefs->priorities[i].nice, niceStr.get());
+
+    // Read the real-time priorities
+    nsPrintfCString realTimeStr("hal.gonk.%s.rt_priority", threadPriorityStr);
+    Preferences::AddIntVarCache(&prefs->priorities[i].realTime,
+                                realTimeStr.get());
+  }
+
+  prefs->initialized = true;
+}
+
 static void
 SetThreadPriority(pid_t aTid, hal::ThreadPriority aThreadPriority)
 {
   // See bug 999115, we can only read preferences on the main thread otherwise
   // we create a race condition in HAL
   MOZ_ASSERT(NS_IsMainThread(), "Can only set thread priorities on main thread");
   MOZ_ASSERT(aThreadPriority >= 0);
 
-  const char* threadPriorityStr;
+  static ThreadPriorityPrefs prefs = { 0 };
+  EnsureThreadPriorityPrefs(&prefs);
+
   switch (aThreadPriority) {
     case THREAD_PRIORITY_COMPOSITOR:
-      threadPriorityStr = ThreadPriorityToString(aThreadPriority);
       break;
     default:
       HAL_ERR("Unrecognized thread priority %d; Doing nothing",
               aThreadPriority);
       return;
   }
 
-  int realTimePriority = Preferences::GetInt(
-    nsPrintfCString("hal.gonk.%s.rt_priority", threadPriorityStr).get());
+  int realTimePriority = prefs.priorities[aThreadPriority].realTime;
 
   if (IsValidRealTimePriority(realTimePriority, SCHED_FIFO)) {
     SetRealTimeThreadPriority(aTid, aThreadPriority, realTimePriority);
     return;
   }
 
-  int niceValue = Preferences::GetInt(
-    nsPrintfCString("hal.gonk.%s.nice", threadPriorityStr).get());
-
-  SetThreadNiceValue(aTid, aThreadPriority, niceValue);
+  SetThreadNiceValue(aTid, aThreadPriority,
+                     prefs.priorities[aThreadPriority].nice);
 }
 
 namespace {
 
 /**
  * This class sets the priority of threads given the kernel thread's id and a
  * value taken from hal::ThreadPriority.
  *
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -834,58 +834,22 @@ js::obj_getOwnPropertyDescriptor(JSConte
     if (!GetFirstArgumentAsObject(cx, args, "Object.getOwnPropertyDescriptor", &obj))
         return false;
     RootedId id(cx);
     if (!ValueToId<CanGC>(cx, args.get(1), &id))
         return false;
     return GetOwnPropertyDescriptor(cx, obj, id, args.rval());
 }
 
-// ES6 draft rev25 (2014/05/22) 19.1.2.14 Object.keys(O)
+// ES6 draft rev27 (2014/08/24) 19.1.2.14 Object.keys(O)
 static bool
 obj_keys(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-
-    // Steps 1-2.
-    RootedObject obj(cx);
-    if (!GetFirstArgumentAsObject(cx, args, "Object.keys", &obj))
-        return false;
-
-    // Steps 3-10. Since JSITER_SYMBOLS and JSITER_HIDDEN are not passed,
-    // GetPropertyNames performs the type check in step 10.c. and the
-    // [[Enumerable]] check specified in step 10.c.iii.
-    AutoIdVector props(cx);
-    if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &props))
-        return false;
-
-    AutoValueVector namelist(cx);
-    if (!namelist.reserve(props.length()))
-        return false;
-    for (size_t i = 0, len = props.length(); i < len; i++) {
-        jsid id = props[i];
-        JSString *str;
-        if (JSID_IS_STRING(id)) {
-            str = JSID_TO_STRING(id);
-        } else {
-            str = Int32ToString<CanGC>(cx, JSID_TO_INT(id));
-            if (!str)
-                return false;
-        }
-        namelist.infallibleAppend(StringValue(str));
-    }
-
-    // Step 11.
-    MOZ_ASSERT(props.length() <= UINT32_MAX);
-    JSObject *aobj = NewDenseCopiedArray(cx, uint32_t(namelist.length()), namelist.begin());
-    if (!aobj)
-        return false;
-
-    args.rval().setObject(*aobj);
-    return true;
+    return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY);
 }
 
 /* ES6 draft 15.2.3.16 */
 static bool
 obj_is(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/strict-compare-same-operands.js
@@ -0,0 +1,49 @@
+function f(l, m) {
+    var a = NaN;
+    var b = 13;
+    var c = "test";
+    var d = undefined;
+    var e = null;
+    var f = 15.7;
+    var g = Math.fround(189777.111);
+    var h = "ABC";
+    var i = String.fromCharCode(65, 65, 65);
+    var j = {};
+    var k = Math.fround("".charCodeAt(15));
+
+    // Special case rigt here:
+    assertEq(a === a, false);
+    assertEq(a !== a, true);
+    assertEq(k === k, false);
+    assertEq(k !== k, true);
+    assertEq(l === l, false);
+    assertEq(l !== l, true);
+
+    assertEq(b === b, true);
+    assertEq(b !== b, false);
+    assertEq(c === c, true);
+    assertEq(c !== c, false);
+    assertEq(d === d, true);
+    assertEq(d !== d, false);
+    assertEq(e === e, true);
+    assertEq(e !== e, false);
+    assertEq(f === f, true);
+    assertEq(f !== f, false);
+    assertEq(g === g, true);
+    assertEq(g !== g, false);
+    assertEq(h === h, true);
+    assertEq(h !== h, false);
+    assertEq(i === i, true);
+    assertEq(i !== i, false);
+    assertEq(j === j, true);
+    assertEq(j !== j, false);
+    assertEq(m === m, true);
+    assertEq(m !== m, false);
+}
+
+function test() {
+    for (var i = 0; i < 100; i++)
+        f("".charCodeAt(15), 42);
+}
+
+test();
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -528,16 +528,26 @@ IsNullOrUndefined(MIRType type)
 }
 
 static inline bool
 IsSimdType(MIRType type)
 {
     return type == MIRType_Int32x4 || type == MIRType_Float32x4;
 };
 
+static inline bool
+IsMagicType(MIRType type)
+{
+    return type == MIRType_MagicHole ||
+           type == MIRType_MagicOptimizedOut ||
+           type == MIRType_MagicIsConstructing ||
+           type == MIRType_MagicOptimizedArguments ||
+           type == MIRType_MagicUninitializedLexical;
+}
+
 // Returns the number of vector elements (hereby called "length") for a given
 // SIMD kind. It is the Y part of the name "Foo x Y".
 static inline unsigned
 SimdTypeToLength(MIRType type)
 {
     MOZ_ASSERT(IsSimdType(type));
     switch (type) {
       case MIRType_Int32x4:
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -2068,21 +2068,30 @@ IonBuilder::inlineToObject(CallInfo &cal
 }
 
 IonBuilder::InliningStatus
 IonBuilder::inlineToInteger(CallInfo &callInfo)
 {
     if (callInfo.argc() != 1 || callInfo.constructing())
         return InliningStatus_NotInlined;
 
-    MIRType type = callInfo.getArg(0)->type();
+    MDefinition *input = callInfo.getArg(0);
 
-    // Only optimize cases where input is number, null, or boolean
-    if (!IsNumberType(type) && type != MIRType_Null && type != MIRType_Boolean)
+    // Only optimize cases where input contains only number, null or boolean
+    if (input->mightBeType(MIRType_Object) ||
+        input->mightBeType(MIRType_String) ||
+        input->mightBeType(MIRType_Symbol) ||
+        input->mightBeType(MIRType_Undefined) ||
+        input->mightBeMagicType())
+    {
         return InliningStatus_NotInlined;
+    }
+
+    MOZ_ASSERT(input->type() == MIRType_Value || input->type() == MIRType_Null ||
+               input->type() == MIRType_Boolean || IsNumberType(input->type()));
 
     // Only optimize cases where output is int32
     if (getInlineReturnType() != MIRType_Int32)
         return InliningStatus_NotInlined;
 
     callInfo.setImplicitlyUsedUnchecked();
 
     MToInt32 *toInt32 = MToInt32::New(alloc(), callInfo.getArg(0));
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -2834,20 +2834,59 @@ MClampToUint8::foldsTo(TempAllocator &al
             int32_t clamped = ClampIntForUint8Array(v.toInt32());
             return MConstant::New(alloc, Int32Value(clamped));
         }
     }
     return this;
 }
 
 bool
+MCompare::tryFoldEqualOperands(bool *result)
+{
+    if (lhs() != rhs())
+        return false;
+
+    // Intuitively somebody would think that if lhs == rhs,
+    // then we can just return true. (Or false for !==)
+    // However NaN !== NaN is true! So we spend some time trying
+    // to eliminate this case.
+
+    if (jsop() != JSOP_STRICTEQ && jsop() != JSOP_STRICTNE)
+        return false;
+
+    if (compareType_ == Compare_Unknown)
+        return false;
+
+    MOZ_ASSERT(compareType_ == Compare_Undefined || compareType_ == Compare_Null ||
+               compareType_ == Compare_Boolean || compareType_ == Compare_Int32 ||
+               compareType_ == Compare_Int32MaybeCoerceBoth ||
+               compareType_ == Compare_Int32MaybeCoerceLHS ||
+               compareType_ == Compare_Int32MaybeCoerceRHS || compareType_ == Compare_UInt32 ||
+               compareType_ == Compare_Double || compareType_ == Compare_DoubleMaybeCoerceLHS ||
+               compareType_ == Compare_DoubleMaybeCoerceRHS || compareType_ == Compare_Float32 ||
+               compareType_ == Compare_String || compareType_ == Compare_StrictString ||
+               compareType_ == Compare_Object || compareType_ == Compare_Value);
+
+    if (isDoubleComparison() || isFloat32Comparison()) {
+        if (!operandsAreNeverNaN())
+            return false;
+    }
+
+    *result = (jsop() == JSOP_STRICTEQ);
+    return true;
+}
+
+bool
 MCompare::tryFold(bool *result)
 {
     JSOp op = jsop();
 
+    if (tryFoldEqualOperands(result))
+        return true;
+
     if (compareType_ == Compare_Null || compareType_ == Compare_Undefined) {
         MOZ_ASSERT(op == JSOP_EQ || op == JSOP_STRICTEQ ||
                    op == JSOP_NE || op == JSOP_STRICTNE);
 
         // The LHS is the value we want to test against null or undefined.
         switch (lhs()->type()) {
           case MIRType_Value:
             return false;
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -591,16 +591,26 @@ class MDefinition : public MNode
             return true;
 
         if (MIRType_Value != this->type())
             return false;
 
         return !resultTypeSet() || resultTypeSet()->mightBeMIRType(type);
     }
 
+    bool mightBeMagicType() const {
+        if (IsMagicType(type()))
+            return true;
+
+        if (MIRType_Value != type())
+            return false;
+
+        return !resultTypeSet() || resultTypeSet()->hasType(types::Type::MagicArgType());
+    }
+
     // Float32 specialization operations (see big comment in IonAnalysis before the Float32
     // specialization algorithm).
     virtual bool isFloat32Commutative() const { return false; }
     virtual bool canProduceFloat32() const { return false; }
     virtual bool canConsumeFloat32(MUse *use) const { return false; }
     virtual void trySpecializeFloat32(TempAllocator &alloc) {}
 #ifdef DEBUG
     // Used during the pass that checks that Float32 flow into valid MDefinitions
@@ -3468,16 +3478,18 @@ class MCompare
         // Both sides of the compare can be Float32
         return compareType_ == Compare_Float32;
     }
 # endif
 
     ALLOW_CLONE(MCompare)
 
   protected:
+    bool tryFoldEqualOperands(bool *result);
+
     bool congruentTo(const MDefinition *ins) const {
         if (!binaryCongruentTo(ins))
             return false;
         return compareType() == ins->toCompare()->compareType() &&
                jsop() == ins->toCompare()->jsop();
     }
 };
 
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Object/keys.js
@@ -0,0 +1,23 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var BUGNUMBER = 1038545;
+var summary = "Coerce the argument passed to Object.keys using ToObject";
+print(BUGNUMBER + ": " + summary);
+
+assertThrowsInstanceOf(() => Object.keys(), TypeError);
+assertThrowsInstanceOf(() => Object.keys(undefined), TypeError);
+assertThrowsInstanceOf(() => Object.keys(null), TypeError);
+
+assertDeepEq(Object.keys(1), []);
+assertDeepEq(Object.keys(true), []);
+if (typeof Symbol === "function") {
+    assertDeepEq(Object.keys(Symbol("foo")), []);
+}
+
+assertDeepEq(Object.keys("foo"), ["0", "1", "2"]);
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -350,24 +350,32 @@ FontFaceSet::Length()
 static PLDHashOperator DestroyIterator(nsPtrHashKey<nsFontFaceLoader>* aKey,
                                        void* aUserArg)
 {
   aKey->GetKey()->Cancel();
   return PL_DHASH_REMOVE;
 }
 
 void
+FontFaceSet::DisconnectFromRule(FontFace* aFontFace)
+{
+  nsCSSFontFaceRule* rule = aFontFace->GetRule();
+  aFontFace->DisconnectFromRule();
+  mRuleFaceMap.Remove(rule);
+}
+
+void
 FontFaceSet::DestroyUserFontSet()
 {
   Disconnect();
   mDocument = nullptr;
   mPresContext = nullptr;
   mLoaders.EnumerateEntries(DestroyIterator, nullptr);
   for (size_t i = 0; i < mRuleFaces.Length(); i++) {
-    mRuleFaces[i].mFontFace->DisconnectFromRule();
+    DisconnectFromRule(mRuleFaces[i].mFontFace);
     mRuleFaces[i].mFontFace->SetUserFontEntry(nullptr);
   }
   for (size_t i = 0; i < mNonRuleFaces.Length(); i++) {
     mNonRuleFaces[i]->SetUserFontEntry(nullptr);
   }
   for (size_t i = 0; i < mUnavailableFaces.Length(); i++) {
     mUnavailableFaces[i]->SetUserFontEntry(nullptr);
   }
@@ -582,17 +590,17 @@ FontFaceSet::UpdateRules(const nsTArray<
         }
       }
 
       // Any left over FontFace objects should also cease being rule backed.
       MOZ_ASSERT(!mUnavailableFaces.Contains(f),
                  "FontFace should not occur in mUnavailableFaces twice");
 
       mUnavailableFaces.AppendElement(f);
-      f->DisconnectFromRule();
+      DisconnectFromRule(f);
     }
   }
 
   if (modified) {
     IncrementGeneration(true);
     mHasLoadingFontFacesIsDirty = true;
     CheckLoadingStarted();
     CheckLoadingFinished();
@@ -978,17 +986,17 @@ FontFaceSet::FindRuleForUserFontEntry(gf
     }
   }
   return nullptr;
 }
 
 gfxUserFontEntry*
 FontFaceSet::FindUserFontEntryForRule(nsCSSFontFaceRule* aRule)
 {
-  FontFace* f = aRule->GetFontFace();
+  FontFace* f = mRuleFaceMap.Get(aRule);
   if (f) {
     return f->GetUserFontEntry();
   }
   return nullptr;
 }
 
 nsresult
 FontFaceSet::LogMessage(gfxUserFontEntry* aUserFontEntry,
@@ -1263,27 +1271,33 @@ FontFaceSet::DoRebuildUserFontSet()
   }
 
   mPresContext->RebuildUserFontSet();
 }
 
 FontFace*
 FontFaceSet::FontFaceForRule(nsCSSFontFaceRule* aRule)
 {
-  FontFace* f = aRule->GetFontFace();
+  MOZ_ASSERT(aRule);
+
+  FontFace* f = mRuleFaceMap.Get(aRule);
   if (f) {
+    MOZ_ASSERT(f->GetFontFaceSet() == this,
+               "existing FontFace is from another FontFaceSet?");
     return f;
   }
 
   // We might be creating a FontFace object for an @font-face rule that we are
   // just about to create a user font entry for, so entry might be null.
   gfxUserFontEntry* entry = FindUserFontEntryForRule(aRule);
   nsRefPtr<FontFace> newFontFace =
     FontFace::CreateForRule(GetParentObject(), mPresContext, aRule, entry);
-  aRule->SetFontFace(newFontFace);
+  MOZ_ASSERT(newFontFace->GetFontFaceSet() == this,
+             "new FontFace is from another FontFaceSet?");
+  mRuleFaceMap.Put(aRule, newFontFace);
   return newFontFace;
 }
 
 void
 FontFaceSet::AddUnavailableFontFace(FontFace* aFontFace)
 {
   MOZ_ASSERT(!aFontFace->HasRule());
   MOZ_ASSERT(!aFontFace->IsInFontFaceSet());
--- a/layout/style/FontFaceSet.h
+++ b/layout/style/FontFaceSet.h
@@ -192,16 +192,22 @@ private:
   bool HasAvailableFontFace(FontFace* aFontFace);
 
   /**
    * Removes any listeners and observers.
    */
   void Disconnect();
 
   /**
+   * Calls DisconnectFromRule on the given FontFace and removes its entry from
+   * mRuleFaceMap.
+   */
+  void DisconnectFromRule(FontFace* aFontFace);
+
+  /**
    * Returns whether there might be any pending font loads, which should cause
    * the mReady Promise not to be resolved yet.
    */
   bool MightHavePendingFontLoads();
 
   /**
    * Checks to see whether it is time to replace mReady and dispatch a
    * "loading" event.
@@ -300,16 +306,23 @@ private:
   // The non rule backed FontFace objects that have been added to this
   // FontFaceSet and their corresponding user font entries.
   nsTArray<nsRefPtr<FontFace>> mNonRuleFaces;
 
   // The non rule backed FontFace objects that have not been added to
   // this FontFaceSet.
   nsTArray<FontFace*> mUnavailableFaces;
 
+  // Map of nsCSSFontFaceRule objects to FontFace objects.  We hold a weak
+  // reference to both; for actively used FontFaces, mRuleFaces will hold
+  // a strong reference to the FontFace and the FontFace will hold on to
+  // the nsCSSFontFaceRule.  FontFaceSet::DisconnectFromRule will ensure its
+  // entry in this array will be removed.
+  nsDataHashtable<nsPtrHashKey<nsCSSFontFaceRule>, FontFace*> mRuleFaceMap;
+
   // The overall status of the loading or loaded fonts in the FontFaceSet.
   mozilla::dom::FontFaceSetLoadStatus mStatus;
 
   // Whether mNonRuleFaces has changed since last time UpdateRules ran.
   bool mNonRuleFacesDirty;
 
   // Whether we have called MaybeResolve() on mReady.
   bool mReadyIsResolved;
--- a/layout/style/test/test_font_loading_api.html
+++ b/layout/style/test/test_font_loading_api.html
@@ -300,16 +300,19 @@ function runTest() {
           is(aError.name, "SyntaxError", "FontFace.loaded with invalid family name " + aFamilyName + " should be rejected with a SyntaxError (TEST 17)");
         });
       });
     });
     return familyTests;
 
   }).then(function() {
 
+    // XXX Disabled this sub-test due to intermittent failures (bug 1076803).
+    return;
+
     // (TEST 18) Test passing valid url() source strings to the FontFace
     // constructor.
     var srcTests = Promise.resolve();
     gCSSFontFaceDescriptors.src.values.forEach(function(aSrc) {
       srcTests = srcTests.then(function() {
         var face = new FontFace("test", aSrc);
         return face.load().then(function() {
           ok(true, "FontFace should load with valid url() src " + aSrc + " (TEST 18)");
--- a/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
@@ -3563,36 +3563,39 @@ status_t MPEG4Source::fragmentedRead(
     bool isSyncSample = false;
     bool newBuffer = false;
     if (mBuffer == NULL) {
         newBuffer = true;
 
         if (mCurrentSampleIndex >= mCurrentSamples.size()) {
             // move to next fragment
             off64_t nextMoof = mNextMoofOffset; // lastSample.offset + lastSample.size;
-
-            // If we're pointing to a sidx box then we skip it.
-            uint32_t hdr[2];
-            if (mDataSource->readAt(nextMoof, hdr, 8) < 8) {
-                return ERROR_END_OF_STREAM;
-            }
-            uint64_t chunk_size = ntohl(hdr[0]);
-            uint32_t chunk_type = ntohl(hdr[1]);
-            if (chunk_type == FOURCC('s', 'i', 'd', 'x')) {
-                nextMoof += chunk_size;
-            }
-
-            mCurrentMoofOffset = nextMoof;
             mCurrentSamples.clear();
             mCurrentSampleIndex = 0;
             mTrackFragmentData.mPresent = false;
-            parseChunk(&nextMoof);
-            if (mCurrentSampleIndex >= mCurrentSamples.size()) {
-                return ERROR_END_OF_STREAM;
-            }
+            uint32_t hdr[2];
+            do {
+                if (mDataSource->readAt(nextMoof, hdr, 8) < 8) {
+                    return ERROR_END_OF_STREAM;
+                }
+                uint64_t chunk_size = ntohl(hdr[0]);
+                uint32_t chunk_type = ntohl(hdr[1]);
+
+                // If we're pointing to a segment type or sidx box then we skip them.
+                if (chunk_type == FOURCC('s', 't', 'y', 'p') ||
+                    chunk_type == FOURCC('s', 'i', 'd', 'x')) {
+                    nextMoof += chunk_size;
+                    continue;
+                }
+                mCurrentMoofOffset = nextMoof;
+                status_t ret = parseChunk(&nextMoof);
+                if (ret != OK) {
+                    return ret;
+                }
+            } while (mCurrentSamples.size() == 0);
 
             if (mTrackFragmentData.mPresent) {
                 mCurrentTime = mTrackFragmentData.mBaseMediaDecodeTime;
             }
         }
 
         const Sample *smpl = &mCurrentSamples[mCurrentSampleIndex];
         offset = smpl->offset;
--- a/media/webrtc/signaling/src/sipcc/core/sdp/sdp_token.c
+++ b/media/webrtc/signaling/src/sipcc/core/sdp/sdp_token.c
@@ -1370,49 +1370,49 @@ sdp_result_e sdp_parse_media (sdp_t *sdp
      * transport/profile types per line, so these are handled differently. */
     if ((mca_p->transport == SDP_TRANSPORT_AAL2_ITU) ||
         (mca_p->transport == SDP_TRANSPORT_AAL2_ATMF) ||
         (mca_p->transport == SDP_TRANSPORT_AAL2_CUSTOM)) {
 
         if (sdp_parse_multiple_profile_payload_types(sdp_p, mca_p, ptr) !=
             SDP_SUCCESS) {
             sdp_p->conf_p->num_invalid_param++;
-	    SDP_FREE(mca_p);
+            SDP_FREE(mca_p);
             return (SDP_INVALID_PARAMETER);
         }
-    } else {
-        /* Transport is a non-AAL2 type.  Parse payloads normally. */
-        sdp_parse_payload_types(sdp_p, mca_p, ptr);
-    }
-
     /* Parse DTLS/SCTP port */
-    if (mca_p->transport == SDP_TRANSPORT_DTLSSCTP) {
+    } else if (mca_p->transport == SDP_TRANSPORT_DTLSSCTP) {
         ptr = sdp_getnextstrtok(ptr, port, sizeof(port), " \t", &result);
         if (result != SDP_SUCCESS) {
             sdp_parse_error(sdp_p->peerconnection,
                 "%s No sctp port specified in m= media line, "
                 "parse failed.", sdp_p->debug_str);
             SDP_FREE(mca_p);
             sdp_p->conf_p->num_invalid_param++;
             return (SDP_INVALID_PARAMETER);
         }
         port_ptr = port;
 
         if (sdp_getchoosetok(port_ptr, &port_ptr, "/ \t", &result)) {
-        	sctp_port = SDP_CHOOSE_PARAM;
+            sctp_port = SDP_CHOOSE_PARAM;
         } else {
-        	sctp_port = sdp_getnextnumtok(port_ptr, (const char **)&port_ptr,
+            sctp_port = sdp_getnextnumtok(port_ptr, (const char **)&port_ptr,
                                            "/ \t", &result);
             if (result != SDP_SUCCESS) {
-            	return (SDP_INVALID_PARAMETER);
+                return (SDP_INVALID_PARAMETER);
             }
             mca_p->sctpport = sctp_port;
         }
+    } else {
+        /* Transport is a non-AAL2 type and not SCTP.  Parse payloads
+           normally. */
+        sdp_parse_payload_types(sdp_p, mca_p, ptr);
     }
 
+
     /* Media line params are valid.  Add it into the SDP. */
     sdp_p->mca_count++;
     if (sdp_p->mca_p == NULL) {
         sdp_p->mca_p = mca_p;
     } else {
         for (next_mca_p = sdp_p->mca_p; next_mca_p->next_p != NULL;
              next_mca_p = next_mca_p->next_p) {
             ; // Empty For
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -31,21 +31,23 @@
 #include "nscore.h"
 #include "nsStackWalk.h"
 
 #include "js/HashTable.h"
 #include "js/Vector.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/HashFunctions.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/JSONWriter.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 
-// CodeAddressService is defined entirely in the header, so this does not make DMD
-// depend on XPCOM's object file.
+// CodeAddressService is defined entirely in the header, so this does not make
+// DMD depend on XPCOM's object file.
 #include "CodeAddressService.h"
 
 // MOZ_REPLACE_ONLY_MEMALIGN saves us from having to define
 // replace_{posix_memalign,aligned_alloc,valloc}.  It requires defining
 // PAGE_SIZE.  Nb: sysconf() is expensive, but it's only used for (the obsolete
 // and rarely used) valloc.
 #define MOZ_REPLACE_ONLY_MEMALIGN 1
 
@@ -216,53 +218,41 @@ StatusMsg(const char* aFmt, ...)
 #endif
   va_end(ap);
 }
 
 /* static */ void
 InfallibleAllocPolicy::ExitOnFailure(const void* aP)
 {
   if (!aP) {
-    StatusMsg("out of memory;  aborting\n");
-    MOZ_CRASH();
+    MOZ_CRASH("DMD out of memory; aborting");
   }
 }
 
-void
-Writer::Write(const char* aFmt, ...) const
+class FpWriteFunc : public JSONWriteFunc
 {
-  va_list ap;
-  va_start(ap, aFmt);
-  mWriterFun(mWriteState, aFmt, ap);
-  va_end(ap);
-}
+public:
+  explicit FpWriteFunc(FILE* aFp) : mFp(aFp) {}
+  ~FpWriteFunc() { fclose(mFp); }
 
-#define W(...) aWriter.Write(__VA_ARGS__);
-
-#define WriteSeparator(...) \
-  W("#-----------------------------------------------------------------\n\n");
+  void Write(const char* aStr) { fputs(aStr, mFp); }
 
-MOZ_EXPORT void
-FpWrite(void* aWriteState, const char* aFmt, va_list aAp)
-{
-  FILE* fp = static_cast<FILE*>(aWriteState);
-  vfprintf(fp, aFmt, aAp);
-}
+private:
+  FILE* mFp;
+};
 
 static double
 Percent(size_t part, size_t whole)
 {
   return (whole == 0) ? 0 : 100 * (double)part / whole;
 }
 
-// Commifies the number and prepends a '~' if requested.  Best used with
-// |kBufLen| and |gBuf[1234]|, because they should be big enough for any number
-// we'll see.
+// Commifies the number.
 static char*
-Show(size_t n, char* buf, size_t buflen, bool addTilde = false)
+Show(size_t n, char* buf, size_t buflen)
 {
   int nc = 0, i = 0, lasti = buflen - 2;
   buf[lasti + 1] = '\0';
   if (n == 0) {
     buf[lasti - i] = '0';
     i++;
   } else {
     while (n > 0) {
@@ -273,37 +263,20 @@ Show(size_t n, char* buf, size_t buflen,
       }
       buf[lasti - i] = static_cast<char>((n % 10) + '0');
       i++;
       n /= 10;
     }
   }
   int firstCharIndex = lasti - i + 1;
 
-  if (addTilde) {
-    firstCharIndex--;
-    buf[firstCharIndex] = '~';
-  }
-
   MOZ_ASSERT(firstCharIndex >= 0);
   return &buf[firstCharIndex];
 }
 
-static const char*
-Plural(size_t aN)
-{
-  return aN == 1 ? "" : "s";
-}
-
-// Used by calls to Show().
-static const size_t kBufLen = 64;
-static char gBuf1[kBufLen];
-static char gBuf2[kBufLen];
-static char gBuf3[kBufLen];
-
 //---------------------------------------------------------------------------
 // Options (Part 1)
 //---------------------------------------------------------------------------
 
 class Options
 {
   template <typename T>
   struct NumOption
@@ -321,32 +294,33 @@ class Options
     Test,     // do some basic correctness tests
     Stress    // do some performance stress tests
   };
 
   char* mDMDEnvVar;   // a saved copy, for later printing
 
   NumOption<size_t>   mSampleBelowSize;
   NumOption<uint32_t> mMaxFrames;
-  NumOption<uint32_t> mMaxRecords;
+  bool mShowDumpStats;
   Mode mMode;
 
   void BadArg(const char* aArg);
   static const char* ValueIfMatch(const char* aArg, const char* aOptionName);
   static bool GetLong(const char* aArg, const char* aOptionName,
-                      long aMin, long aMax, long* aN);
+                      long aMin, long aMax, long* aValue);
+  static bool GetBool(const char* aArg, const char* aOptionName, bool* aValue);
 
 public:
   explicit Options(const char* aDMDEnvVar);
 
   const char* DMDEnvVar() const { return mDMDEnvVar; }
 
   size_t SampleBelowSize() const { return mSampleBelowSize.mActual; }
   size_t MaxFrames()       const { return mMaxFrames.mActual; }
-  size_t MaxRecords()      const { return mMaxRecords.mActual; }
+  size_t ShowDumpStats()   const { return mShowDumpStats; }
 
   void SetSampleBelowSize(size_t aN) { mSampleBelowSize.mActual = aN; }
 
   bool IsTestMode()   const { return mMode == Test; }
   bool IsStressMode() const { return mMode == Stress; }
 };
 
 static Options *gOptions;
@@ -673,40 +647,37 @@ typedef CodeAddressService<StringTable, 
 //---------------------------------------------------------------------------
 
 class StackTrace
 {
 public:
   static const uint32_t MaxFrames = 24;
 
 private:
-  uint32_t mLength;         // The number of PCs.
-  void* mPcs[MaxFrames];    // The PCs themselves.  If --max-frames is less
-                            // than 24, this array is bigger than necessary,
-                            // but that case is unusual.
+  uint32_t mLength;             // The number of PCs.
+  const void* mPcs[MaxFrames];  // The PCs themselves.  If --max-frames is less
+                                // than 24, this array is bigger than
+                                // necessary, but that case is unusual.
 
 public:
   StackTrace() : mLength(0) {}
 
   uint32_t Length() const { return mLength; }
-  void* Pc(uint32_t i) const { MOZ_ASSERT(i < mLength); return mPcs[i]; }
+  const void* Pc(uint32_t i) const
+  {
+    MOZ_ASSERT(i < mLength);
+    return mPcs[i];
+  }
 
   uint32_t Size() const { return mLength * sizeof(mPcs[0]); }
 
   // The stack trace returned by this function is interned in gStackTraceTable,
   // and so is immortal and unmovable.
   static const StackTrace* Get(Thread* aT);
 
-  void Sort()
-  {
-    qsort(mPcs, mLength, sizeof(mPcs[0]), StackTrace::Cmp);
-  }
-
-  void Print(const Writer& aWriter, CodeAddressService* aLocService) const;
-
   // Hash policy.
 
   typedef StackTrace* Lookup;
 
   static uint32_t hash(const StackTrace* const& aSt)
   {
     return mozilla::HashBytes(aSt->mPcs, aSt->Size());
   }
@@ -714,57 +685,45 @@ public:
   static bool match(const StackTrace* const& aA,
                     const StackTrace* const& aB)
   {
     return aA->mLength == aB->mLength &&
            memcmp(aA->mPcs, aB->mPcs, aA->Size()) == 0;
   }
 
 private:
-  static void StackWalkCallback(void* aPc, void* aSp, void* aClosure)
+  static void StackWalkCallback(uint32_t aFrameNumber, void* aPc, void* aSp,
+                                void* aClosure)
   {
     StackTrace* st = (StackTrace*) aClosure;
     MOZ_ASSERT(st->mLength < MaxFrames);
     st->mPcs[st->mLength] = aPc;
     st->mLength++;
-  }
-
-  static int Cmp(const void* aA, const void* aB)
-  {
-    const void* const a = *static_cast<const void* const*>(aA);
-    const void* const b = *static_cast<const void* const*>(aB);
-    if (a < b) return -1;
-    if (a > b) return  1;
-    return 0;
+    MOZ_ASSERT(st->mLength == aFrameNumber);
   }
 };
 
 typedef js::HashSet<StackTrace*, StackTrace, InfallibleAllocPolicy>
         StackTraceTable;
 static StackTraceTable* gStackTraceTable = nullptr;
 
+typedef js::HashSet<const StackTrace*, js::DefaultHasher<const StackTrace*>,
+                    InfallibleAllocPolicy>
+        StackTraceSet;
+
+typedef js::HashSet<const void*, js::DefaultHasher<const void*>,
+                    InfallibleAllocPolicy>
+        PointerSet;
+typedef js::HashMap<const void*, uint32_t, js::DefaultHasher<const void*>,
+                    InfallibleAllocPolicy>
+        PointerIdMap;
+
 // We won't GC the stack trace table until it this many elements.
 static uint32_t gGCStackTraceTableWhenSizeExceeds = 4 * 1024;
 
-void
-StackTrace::Print(const Writer& aWriter, CodeAddressService* aLocService) const
-{
-  if (mLength == 0) {
-    W("    (empty)\n");  // StackTrace::Get() must have failed
-    return;
-  }
-
-  static const size_t buflen = 1024;
-  char buf[buflen];
-  for (uint32_t i = 0; i < mLength; i++) {
-    aLocService->GetLocation(Pc(i), buf, buflen);
-    aWriter.Write("    %s\n", buf);
-  }
-}
-
 /* static */ const StackTrace*
 StackTrace::Get(Thread* aT)
 {
   MOZ_ASSERT(gStateLock->IsLocked());
   MOZ_ASSERT(aT->InterceptsAreBlocked());
 
   // On Windows, NS_StackWalk can acquire a lock from the shared library
   // loader.  Another thread might call malloc while holding that lock (when
@@ -792,19 +751,19 @@ StackTrace::Get(Thread* aT)
     // attempt to create a semaphore (which can happen in printf) could
     // deadlock.
     //
     // However, the most complex thing DMD does after Get() returns is to put
     // something in a hash table, which might call
     // InfallibleAllocPolicy::malloc_.  I'm not yet sure if this needs special
     // handling, hence the forced abort.  Sorry.  If you hit this, please file
     // a bug and CC nnethercote.
-    MOZ_CRASH();
+    MOZ_CRASH("unexpected case in StackTrace::Get()");
   } else {
-    MOZ_CRASH();  // should be impossible
+    MOZ_CRASH("impossible case in StackTrace::Get()");
   }
 
   StackTraceTable::AddPtr p = gStackTraceTable->lookupForAdd(&tmp);
   if (!p) {
     StackTrace* stnew = InfallibleAllocPolicy::new_<StackTrace>(tmp);
     (void)gStackTraceTable->add(p, stnew);
   }
   return *p;
@@ -893,16 +852,18 @@ public:
     : mPtr(aPtr),
       mReqSize(aReqSize),
       mAllocStackTrace_mSampled(aAllocStackTrace, aSampled),
       mReportStackTrace_mReportedOnAlloc()     // all fields get zeroed
   {
     MOZ_ASSERT(aAllocStackTrace);
   }
 
+  const void* Address() const { return mPtr; }
+
   size_t ReqSize() const { return mReqSize; }
 
   // Sampled blocks always have zero slop.
   size_t SlopSize() const
   {
     return IsSampled() ? 0 : MallocSizeOf(mPtr) - mReqSize;
   }
 
@@ -916,33 +877,50 @@ public:
     return mAllocStackTrace_mSampled.Tag();
   }
 
   const StackTrace* AllocStackTrace() const
   {
     return mAllocStackTrace_mSampled.Ptr();
   }
 
-  const StackTrace* ReportStackTrace1() const {
+  const StackTrace* ReportStackTrace1() const
+  {
     return mReportStackTrace_mReportedOnAlloc[0].Ptr();
   }
 
-  const StackTrace* ReportStackTrace2() const {
+  const StackTrace* ReportStackTrace2() const
+  {
     return mReportStackTrace_mReportedOnAlloc[1].Ptr();
   }
 
-  bool ReportedOnAlloc1() const {
+  bool ReportedOnAlloc1() const
+  {
     return mReportStackTrace_mReportedOnAlloc[0].Tag();
   }
 
-  bool ReportedOnAlloc2() const {
+  bool ReportedOnAlloc2() const
+  {
     return mReportStackTrace_mReportedOnAlloc[1].Tag();
   }
 
-  uint32_t NumReports() const {
+  void AddStackTracesToTable(StackTraceSet& aStackTraces) const
+  {
+    aStackTraces.put(AllocStackTrace());  // never null
+    const StackTrace* st;
+    if ((st = ReportStackTrace1())) {     // may be null
+      aStackTraces.put(st);
+    }
+    if ((st = ReportStackTrace2())) {     // may be null
+      aStackTraces.put(st);
+    }
+  }
+
+  uint32_t NumReports() const
+  {
     if (ReportStackTrace2()) {
       MOZ_ASSERT(ReportStackTrace1());
       return 2;
     }
     if (ReportStackTrace1()) {
       return 1;
     }
     return 0;
@@ -989,60 +967,47 @@ public:
   {
     return aB.mPtr == aPtr;
   }
 };
 
 typedef js::HashSet<Block, Block, InfallibleAllocPolicy> BlockTable;
 static BlockTable* gBlockTable = nullptr;
 
-typedef js::HashSet<const StackTrace*, js::DefaultHasher<const StackTrace*>,
-                    InfallibleAllocPolicy>
-        StackTraceSet;
-
 // Add a pointer to each live stack trace into the given StackTraceSet.  (A
 // stack trace is live if it's used by one of the live blocks.)
 static void
 GatherUsedStackTraces(StackTraceSet& aStackTraces)
 {
   MOZ_ASSERT(gStateLock->IsLocked());
   MOZ_ASSERT(Thread::Fetch()->InterceptsAreBlocked());
 
   aStackTraces.finish();
-  aStackTraces.init(1024);
+  aStackTraces.init(512);
 
   for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) {
     const Block& b = r.front();
-    aStackTraces.put(b.AllocStackTrace());
-    aStackTraces.put(b.ReportStackTrace1());
-    aStackTraces.put(b.ReportStackTrace2());
+    b.AddStackTracesToTable(aStackTraces);
   }
-
-  // Any of the stack traces added above may have been null.  For the sake of
-  // cleanliness, don't leave the null pointer in the set.
-  aStackTraces.remove(nullptr);
 }
 
 // Delete stack traces that we aren't using, and compact our hashtable.
 static void
 GCStackTraces()
 {
   MOZ_ASSERT(gStateLock->IsLocked());
   MOZ_ASSERT(Thread::Fetch()->InterceptsAreBlocked());
 
   StackTraceSet usedStackTraces;
   GatherUsedStackTraces(usedStackTraces);
 
   // Delete all unused stack traces from gStackTraceTable.  The Enum destructor
   // will automatically rehash and compact the table.
-  for (StackTraceTable::Enum e(*gStackTraceTable);
-       !e.empty();
-       e.popFront()) {
+  for (StackTraceTable::Enum e(*gStackTraceTable); !e.empty(); e.popFront()) {
     StackTrace* const& st = e.front();
-
     if (!usedStackTraces.has(st)) {
       e.removeFront();
       InfallibleAllocPolicy::delete_(st);
     }
   }
 
   // Schedule a GC when we have twice as many stack traces as we had right after
   // this GC finished.
@@ -1245,203 +1210,16 @@ replace_free(void* aPtr)
   FreeCallback(aPtr, t);
   gMallocTable->free(aPtr);
 }
 
 namespace mozilla {
 namespace dmd {
 
 //---------------------------------------------------------------------------
-// Heap block records
-//---------------------------------------------------------------------------
-
-class RecordKey
-{
-public:
-  const StackTrace* const mAllocStackTrace;   // never null
-protected:
-  const StackTrace* const mReportStackTrace1; // nullptr if unreported
-  const StackTrace* const mReportStackTrace2; // nullptr if not 2x-reported
-
-public:
-  explicit RecordKey(const Block& aB)
-    : mAllocStackTrace(aB.AllocStackTrace()),
-      mReportStackTrace1(aB.ReportStackTrace1()),
-      mReportStackTrace2(aB.ReportStackTrace2())
-  {
-    MOZ_ASSERT(mAllocStackTrace);
-  }
-
-  // Hash policy.
-
-  typedef RecordKey Lookup;
-
-  static uint32_t hash(const RecordKey& aKey)
-  {
-    return mozilla::HashGeneric(aKey.mAllocStackTrace,
-                                aKey.mReportStackTrace1,
-                                aKey.mReportStackTrace2);
-  }
-
-  static bool match(const RecordKey& aA, const RecordKey& aB)
-  {
-    return aA.mAllocStackTrace   == aB.mAllocStackTrace &&
-           aA.mReportStackTrace1 == aB.mReportStackTrace1 &&
-           aA.mReportStackTrace2 == aB.mReportStackTrace2;
-  }
-};
-
-class RecordSize
-{
-  static const size_t kReqBits = sizeof(size_t) * 8 - 1;  // 31 or 63
-
-  size_t mReq;              // size requested
-  size_t mSlop:kReqBits;    // slop bytes
-  size_t mSampled:1;        // were one or more blocks contributing to this
-                            //   RecordSize sampled?
-public:
-  RecordSize()
-    : mReq(0),
-      mSlop(0),
-      mSampled(false)
-  {}
-
-  size_t Req()    const { return mReq; }
-  size_t Slop()   const { return mSlop; }
-  size_t Usable() const { return mReq + mSlop; }
-
-  bool IsSampled() const { return mSampled; }
-
-  void Add(const Block& aB)
-  {
-    mReq  += aB.ReqSize();
-    mSlop += aB.SlopSize();
-    mSampled = mSampled || aB.IsSampled();
-  }
-
-  void Add(const RecordSize& aRecordSize)
-  {
-    mReq  += aRecordSize.Req();
-    mSlop += aRecordSize.Slop();
-    mSampled = mSampled || aRecordSize.IsSampled();
-  }
-
-  static int CmpByUsable(const RecordSize& aA, const RecordSize& aB)
-  {
-    // Primary sort: put bigger usable sizes first.
-    if (aA.Usable() > aB.Usable()) return -1;
-    if (aA.Usable() < aB.Usable()) return  1;
-
-    // Secondary sort: put bigger requested sizes first.
-    if (aA.Req() > aB.Req()) return -1;
-    if (aA.Req() < aB.Req()) return  1;
-
-    // Tertiary sort: put non-sampled records before sampled records.
-    if (!aA.mSampled &&  aB.mSampled) return -1;
-    if ( aA.mSampled && !aB.mSampled) return  1;
-
-    return 0;
-  }
-};
-
-// A collection of one or more heap blocks with a common RecordKey.
-class Record : public RecordKey
-{
-  // The RecordKey base class serves as the key in RecordTables.  These two
-  // fields constitute the value, so it's ok for them to be |mutable|.
-  mutable uint32_t    mNumBlocks; // number of blocks with this RecordKey
-  mutable RecordSize mRecordSize; // combined size of those blocks
-
-public:
-  explicit Record(const RecordKey& aKey)
-    : RecordKey(aKey),
-      mNumBlocks(0),
-      mRecordSize()
-  {}
-
-  uint32_t NumBlocks() const { return mNumBlocks; }
-
-  const RecordSize& GetRecordSize() const { return mRecordSize; }
-
-  // This is |const| thanks to the |mutable| fields above.
-  void Add(const Block& aB) const
-  {
-    mNumBlocks++;
-    mRecordSize.Add(aB);
-  }
-
-  void Print(const Writer& aWriter, CodeAddressService* aLocService,
-             uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
-             size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
-             size_t aTotalUsableSize, bool aShowCategoryPercentage,
-             bool aShowReportedAt) const;
-
-  static int CmpByUsable(const void* aA, const void* aB)
-  {
-    const Record* const a = *static_cast<const Record* const*>(aA);
-    const Record* const b = *static_cast<const Record* const*>(aB);
-
-    return RecordSize::CmpByUsable(a->mRecordSize, b->mRecordSize);
-  }
-};
-
-typedef js::HashSet<Record, Record, InfallibleAllocPolicy> RecordTable;
-
-void
-Record::Print(const Writer& aWriter, CodeAddressService* aLocService,
-              uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
-              size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
-              size_t aTotalUsableSize, bool aShowCategoryPercentage,
-              bool aShowReportedAt) const
-{
-  bool showTilde = mRecordSize.IsSampled();
-
-  W("%s {\n", aStr);
-  W("  %s block%s in heap block record %s of %s\n",
-    Show(mNumBlocks, gBuf1, kBufLen, showTilde), Plural(mNumBlocks),
-    Show(aM, gBuf2, kBufLen),
-    Show(aN, gBuf3, kBufLen));
-
-  W("  %s bytes (%s requested / %s slop)\n",
-    Show(mRecordSize.Usable(), gBuf1, kBufLen, showTilde),
-    Show(mRecordSize.Req(),    gBuf2, kBufLen, showTilde),
-    Show(mRecordSize.Slop(),   gBuf3, kBufLen, showTilde));
-
-  W("  %4.2f%% of the heap (%4.2f%% cumulative)\n",
-    Percent(mRecordSize.Usable(), aTotalUsableSize),
-    Percent(aCumulativeUsableSize, aTotalUsableSize));
-
-  if (aShowCategoryPercentage) {
-    W("  %4.2f%% of %s (%4.2f%% cumulative)\n",
-      Percent(mRecordSize.Usable(), aCategoryUsableSize),
-      astr,
-      Percent(aCumulativeUsableSize, aCategoryUsableSize));
-  }
-
-  W("  Allocated at {\n");
-  mAllocStackTrace->Print(aWriter, aLocService);
-  W("  }\n");
-
-  if (aShowReportedAt) {
-    if (mReportStackTrace1) {
-      W("  Reported at {\n");
-      mReportStackTrace1->Print(aWriter, aLocService);
-      W("  }\n");
-    }
-    if (mReportStackTrace2) {
-      W("  Reported again at {\n");
-      mReportStackTrace2->Print(aWriter, aLocService);
-      W("  }\n");
-    }
-  }
-
-  W("}\n\n");
-}
-
-//---------------------------------------------------------------------------
 // Options (Part 2)
 //---------------------------------------------------------------------------
 
 // Given an |aOptionName| like "foo", succeed if |aArg| has the form "foo=blah"
 // (where "blah" is non-empty) and return the pointer to "blah".  |aArg| can
 // have leading space chars (but not other whitespace).
 const char*
 Options::ValueIfMatch(const char* aArg, const char* aOptionName)
@@ -1454,23 +1232,41 @@ Options::ValueIfMatch(const char* aArg, 
   }
   return nullptr;
 }
 
 // Extracts a |long| value for an option from an argument.  It must be within
 // the range |aMin..aMax| (inclusive).
 bool
 Options::GetLong(const char* aArg, const char* aOptionName,
-                 long aMin, long aMax, long* aN)
+                 long aMin, long aMax, long* aValue)
 {
   if (const char* optionValue = ValueIfMatch(aArg, aOptionName)) {
     char* endPtr;
-    *aN = strtol(optionValue, &endPtr, /* base */ 10);
-    if (!*endPtr && aMin <= *aN && *aN <= aMax &&
-        *aN != LONG_MIN && *aN != LONG_MAX) {
+    *aValue = strtol(optionValue, &endPtr, /* base */ 10);
+    if (!*endPtr && aMin <= *aValue && *aValue <= aMax &&
+        *aValue != LONG_MIN && *aValue != LONG_MAX) {
+      return true;
+    }
+  }
+  return false;
+}
+
+// Extracts a |bool| value for an option -- encoded as "yes" or "no" -- from an
+// argument.
+bool
+Options::GetBool(const char* aArg, const char* aOptionName, bool* aValue)
+{
+  if (const char* optionValue = ValueIfMatch(aArg, aOptionName)) {
+    if (strcmp(optionValue, "yes") == 0) {
+      *aValue = true;
+      return true;
+    }
+    if (strcmp(optionValue, "no") == 0) {
+      *aValue = false;
       return true;
     }
   }
   return false;
 }
 
 // The sample-below default is a prime number close to 4096.
 // - Why that size?  Because it's *much* faster but only moderately less precise
@@ -1479,17 +1275,17 @@ Options::GetLong(const char* aArg, const
 //   of 4096, for example, then our alloc counter would only take on even
 //   values, because jemalloc always rounds up requests sizes.  In contrast, a
 //   prime size will explore all possible values of the alloc counter.
 //
 Options::Options(const char* aDMDEnvVar)
   : mDMDEnvVar(InfallibleAllocPolicy::strdup_(aDMDEnvVar)),
     mSampleBelowSize(4093, 100 * 100 * 1000),
     mMaxFrames(StackTrace::MaxFrames, StackTrace::MaxFrames),
-    mMaxRecords(1000, 1000000),
+    mShowDumpStats(false),
     mMode(Normal)
 {
   char* e = mDMDEnvVar;
   if (strcmp(e, "1") != 0) {
     bool isEnd = false;
     while (!isEnd) {
       // Consume leading whitespace.
       while (isspace(*e)) {
@@ -1505,24 +1301,25 @@ Options::Options(const char* aDMDEnvVar)
         e++;
       }
       char replacedChar = *e;
       isEnd = replacedChar == '\0';
       *e = '\0';
 
       // Handle arg
       long myLong;
+      bool myBool;
       if (GetLong(arg, "--sample-below", 1, mSampleBelowSize.mMax, &myLong)) {
         mSampleBelowSize.mActual = myLong;
 
       } else if (GetLong(arg, "--max-frames", 1, mMaxFrames.mMax, &myLong)) {
         mMaxFrames.mActual = myLong;
 
-      } else if (GetLong(arg, "--max-records", 1, mMaxRecords.mMax, &myLong)) {
-        mMaxRecords.mActual = myLong;
+      } else if (GetBool(arg, "--show-dump-stats", &myBool)) {
+        mShowDumpStats = myBool;
 
       } else if (strcmp(arg, "--mode=normal") == 0) {
         mMode = Options::Normal;
       } else if (strcmp(arg, "--mode=test")   == 0) {
         mMode = Options::Test;
       } else if (strcmp(arg, "--mode=stress") == 0) {
         mMode = Options::Stress;
 
@@ -1555,49 +1352,49 @@ Options::BadArg(const char* aArg)
   StatusMsg("The following options are allowed;  defaults are shown in [].\n");
   StatusMsg("  --sample-below=<1..%d> Sample blocks smaller than this [%d]\n",
             int(mSampleBelowSize.mMax),
             int(mSampleBelowSize.mDefault));
   StatusMsg("                               (prime numbers are recommended)\n");
   StatusMsg("  --max-frames=<1..%d>         Max. depth of stack traces [%d]\n",
             int(mMaxFrames.mMax),
             int(mMaxFrames.mDefault));
-  StatusMsg("  --max-records=<1..%u>   Max. number of records printed [%u]\n",
-            mMaxRecords.mMax,
-            mMaxRecords.mDefault);
+  StatusMsg("  --show-dump-stats=<yes|no>   Show stats about dumps? [no]\n");
   StatusMsg("  --mode=<normal|test|stress>  Mode of operation [normal]\n");
   StatusMsg("\n");
   exit(1);
 }
 
 //---------------------------------------------------------------------------
 // DMD start-up
 //---------------------------------------------------------------------------
 
 #ifdef XP_MACOSX
 static void
-NopStackWalkCallback(void* aPc, void* aSp, void* aClosure)
+NopStackWalkCallback(uint32_t aFrameNumber, void* aPc, void* aSp,
+                     void* aClosure)
 {
 }
 #endif
 
 // Note that fopen() can allocate.
 static FILE*
 OpenOutputFile(const char* aFilename)
 {
   FILE* fp = fopen(aFilename, "w");
   if (!fp) {
     StatusMsg("can't create %s file: %s\n", aFilename, strerror(errno));
     exit(1);
   }
   return fp;
 }
 
-static void RunTestMode(FILE* fp);
-static void RunStressMode(FILE* fp);
+static void RunTestMode(UniquePtr<FpWriteFunc> aF1, UniquePtr<FpWriteFunc> aF2,
+                        UniquePtr<FpWriteFunc> aF3, UniquePtr<FpWriteFunc> aF4);
+static void RunStressMode(UniquePtr<FpWriteFunc> aF);
 
 // WARNING: this function runs *very* early -- before all static initializers
 // have run.  For this reason, non-scalar globals such as gStateLock and
 // gStackTraceTable are allocated dynamically (so we can guarantee their
 // construction in this function) rather than statically.
 static void
 Init(const malloc_table_t* aMallocTable)
 {
@@ -1645,37 +1442,38 @@ Init(const malloc_table_t* aMallocTable)
     gStackTraceTable = InfallibleAllocPolicy::new_<StackTraceTable>();
     gStackTraceTable->init(8192);
 
     gBlockTable = InfallibleAllocPolicy::new_<BlockTable>();
     gBlockTable->init(8192);
   }
 
   if (gOptions->IsTestMode()) {
-    // OpenOutputFile() can allocate.  So do this before setting
-    // gIsDMDRunning so those allocations don't show up in our results.  Once
-    // gIsDMDRunning is set we are intercepting malloc et al. in earnest.
-    FILE* fp = OpenOutputFile("test.dmd");
+    // Do all necessary allocations before setting gIsDMDRunning so those
+    // allocations don't show up in our results.  Once gIsDMDRunning is set we
+    // are intercepting malloc et al. in earnest.
+    auto f1 = MakeUnique<FpWriteFunc>(OpenOutputFile("full1.json"));
+    auto f2 = MakeUnique<FpWriteFunc>(OpenOutputFile("full2.json"));
+    auto f3 = MakeUnique<FpWriteFunc>(OpenOutputFile("full3.json"));
+    auto f4 = MakeUnique<FpWriteFunc>(OpenOutputFile("full4.json"));
     gIsDMDRunning = true;
 
     StatusMsg("running test mode...\n");
-    RunTestMode(fp);
+    RunTestMode(Move(f1), Move(f2), Move(f3), Move(f4));
     StatusMsg("finished test mode\n");
-    fclose(fp);
     exit(0);
   }
 
   if (gOptions->IsStressMode()) {
-    FILE* fp = OpenOutputFile("stress.dmd");
+    auto f = MakeUnique<FpWriteFunc>(OpenOutputFile("stress.json"));
     gIsDMDRunning = true;
 
     StatusMsg("running stress mode...\n");
-    RunStressMode(fp);
+    RunStressMode(Move(f));
     StatusMsg("finished stress mode\n");
-    fclose(fp);
     exit(0);
   }
 
   gIsDMDRunning = true;
 }
 
 //---------------------------------------------------------------------------
 // DMD reporting and unreporting
@@ -1714,66 +1512,23 @@ ReportOnAlloc(const void* aPtr)
 {
   ReportHelper(aPtr, /* onAlloc */ true);
 }
 
 //---------------------------------------------------------------------------
 // DMD output
 //---------------------------------------------------------------------------
 
-static void
-PrintSortedRecords(const Writer& aWriter, CodeAddressService* aLocService,
-                   int (*aCmp)(const void*, const void*),
-                   const char* aStr, const char* astr,
-                   const RecordTable& aRecordTable,
-                   size_t aCategoryUsableSize, size_t aTotalUsableSize,
-                   bool aShowCategoryPercentage, bool aShowReportedAt)
-{
-  StatusMsg("  creating and sorting %s heap block record array...\n", astr);
-
-  // Convert the table into a sorted array.
-  js::Vector<const Record*, 0, InfallibleAllocPolicy> recordArray;
-  recordArray.reserve(aRecordTable.count());
-  for (RecordTable::Range r = aRecordTable.all();
-       !r.empty();
-       r.popFront()) {
-    recordArray.infallibleAppend(&r.front());
-  }
-  qsort(recordArray.begin(), recordArray.length(), sizeof(recordArray[0]),
-        aCmp);
-
-  WriteSeparator();
-
-  if (recordArray.length() == 0) {
-    W("# no %s heap blocks\n\n", astr);
-    return;
-  }
-
-  StatusMsg("  printing %s heap block record array...\n", astr);
-  size_t cumulativeUsableSize = 0;
-
-  // Limit the number of records printed, because fix_linux_stack.py is too
-  // damn slow.  Note that we don't break out of this loop because we need to
-  // keep adding to |cumulativeUsableSize|.
-  uint32_t numRecords = recordArray.length();
-  uint32_t maxRecords = gOptions->MaxRecords();
-  for (uint32_t i = 0; i < numRecords; i++) {
-    const Record* r = recordArray[i];
-    cumulativeUsableSize += r->GetRecordSize().Usable();
-    if (i < maxRecords) {
-      r->Print(aWriter, aLocService, i+1, numRecords, aStr, astr,
-               aCategoryUsableSize, cumulativeUsableSize, aTotalUsableSize,
-               aShowCategoryPercentage, aShowReportedAt);
-    } else if (i == maxRecords) {
-      W("# %s: stopping after %s heap block records\n\n", aStr,
-        Show(maxRecords, gBuf1, kBufLen));
-    }
-  }
-  MOZ_ASSERT(aCategoryUsableSize == cumulativeUsableSize);
-}
+// The version number of the output format. Increment this if you make
+// backwards-incompatible changes to the format.
+//
+// Version history:
+// - 1: The original format (bug 1044709).
+//
+static const int kOutputVersionNumber = 1;
 
 // Note that, unlike most SizeOf* functions, this function does not take a
 // |mozilla::MallocSizeOf| argument.  That's because those arguments are
 // primarily to aid DMD track heap blocks... but DMD deliberately doesn't track
 // heap blocks it allocated for itself!
 //
 // SizeOfInternal should be called while you're holding the state lock and
 // while intercepts are blocked; SizeOf acquires the lock and blocks
@@ -1844,335 +1599,254 @@ ClearReports()
 }
 
 MOZ_EXPORT bool
 IsRunning()
 {
   return gIsDMDRunning;
 }
 
-// AnalyzeReports() and AnalyzeHeap() have a lot in common. This abstract class
-// encapsulates the operations that are not shared.
-class Analyzer
-{
-public:
-  virtual const char* AnalyzeFunctionName() const = 0;
-
-  virtual RecordTable* ProcessBlock(const Block& aBlock) = 0;
-
-  virtual void PrintRecords(const Writer& aWriter,
-                            CodeAddressService* aLocService) const = 0;
-  virtual void PrintSummary(const Writer& aWriter, bool aShowTilde) const = 0;
-  virtual void PrintStats(const Writer& aWriter) const = 0;
-
-  struct RecordKindData
-  {
-    RecordTable mRecordTable;
-    size_t mUsableSize;
-    size_t mNumBlocks;
-
-    explicit RecordKindData(size_t aN)
-      : mUsableSize(0), mNumBlocks(0)
-    {
-      mRecordTable.init(aN);
-    }
-
-    void processBlock(const Block& aBlock)
-    {
-      mUsableSize += aBlock.UsableSize();
-      mNumBlocks++;
-    }
-  };
-};
-
-class ReportsAnalyzer MOZ_FINAL : public Analyzer
+// This function converts an integer to base-32. |aBuf| must have space for at
+// least eight chars, which is the space needed to hold 'Dffffff' (including
+// the terminating null char), which is the base-32 representation of
+// 0xffffffff.
+//
+// We use base-32 values for indexing into the traceTable and the frameTable,
+// for the following reasons.
+//
+// - Base-32 gives more compact indices than base-16.
+//
+// - 32 is a power-of-two, which makes the necessary div/mod calculations fast.
+//
+// - We can (and do) choose non-numeric digits for base-32. When
+//   inspecting/debugging the JSON output, non-numeric indices are easier to
+//   search for than numeric indices.
+//
+char* Base32(uint32_t aN, char* aBuf, size_t aBufLen)
 {
-  RecordKindData mUnreported;
-  RecordKindData mOnceReported;
-  RecordKindData mTwiceReported;
-
-  size_t mTotalUsableSize;
-  size_t mTotalNumBlocks;
-
-public:
-  ReportsAnalyzer()
-    : mUnreported(1024), mOnceReported(1024), mTwiceReported(0),
-      mTotalUsableSize(0), mTotalNumBlocks(0)
-  {}
-
-  ~ReportsAnalyzer()
-  {
-    ClearReports();
-  }
-
-  virtual const char* AnalyzeFunctionName() const { return "AnalyzeReports"; }
-
-  virtual RecordTable* ProcessBlock(const Block& aBlock)
-  {
-    RecordKindData* data;
-    uint32_t numReports = aBlock.NumReports();
-    if (numReports == 0) {
-      data = &mUnreported;
-    } else if (numReports == 1) {
-      data = &mOnceReported;
-    } else {
-      MOZ_ASSERT(numReports == 2);
-      data = &mTwiceReported;
-    }
-    data->processBlock(aBlock);
-
-    mTotalUsableSize += aBlock.UsableSize();
-    mTotalNumBlocks++;
-
-    return &data->mRecordTable;
-  }
-
-  virtual void PrintRecords(const Writer& aWriter,
-                            CodeAddressService* aLocService) const
-  {
-    PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
-                       "Twice-reported", "twice-reported",
-                       mTwiceReported.mRecordTable,
-                       mTwiceReported.mUsableSize, mTotalUsableSize,
-                       /* showCategoryPercentage = */ true,
-                       /* showReportedAt = */ true);
-
-    PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
-                       "Unreported", "unreported",
-                       mUnreported.mRecordTable,
-                       mUnreported.mUsableSize, mTotalUsableSize,
-                       /* showCategoryPercentage = */ true,
-                       /* showReportedAt = */ true);
+  static const char digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef";
 
-    PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
-                       "Once-reported", "once-reported",
-                       mOnceReported.mRecordTable,
-                       mOnceReported.mUsableSize, mTotalUsableSize,
-                       /* showCategoryPercentage = */ true,
-                       /* showReportedAt = */ true);
-  }
-
-  virtual void PrintSummary(const Writer& aWriter, bool aShowTilde) const
-  {
-    W("  Total:          %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
-      Show(mTotalUsableSize, gBuf1, kBufLen, aShowTilde),
-      100.0,
-      Show(mTotalNumBlocks,  gBuf2, kBufLen, aShowTilde),
-      100.0);
-
-    W("  Unreported:     %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
-      Show(mUnreported.mUsableSize, gBuf1, kBufLen, aShowTilde),
-      Percent(mUnreported.mUsableSize, mTotalUsableSize),
-      Show(mUnreported.mNumBlocks, gBuf2, kBufLen, aShowTilde),
-      Percent(mUnreported.mNumBlocks, mTotalNumBlocks));
+  char* b = aBuf + aBufLen - 1;
+  *b = '\0';
+  do {
+    b--;
+    if (b == aBuf) {
+      MOZ_CRASH("Base32 buffer too small");
+    }
+    *b = digits[aN % 32];
+    aN /= 32;
+  } while (aN);
 
-    W("  Once-reported:  %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
-      Show(mOnceReported.mUsableSize, gBuf1, kBufLen, aShowTilde),
-      Percent(mOnceReported.mUsableSize, mTotalUsableSize),
-      Show(mOnceReported.mNumBlocks, gBuf2, kBufLen, aShowTilde),
-      Percent(mOnceReported.mNumBlocks, mTotalNumBlocks));
-
-    W("  Twice-reported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
-      Show(mTwiceReported.mUsableSize, gBuf1, kBufLen, aShowTilde),
-      Percent(mTwiceReported.mUsableSize, mTotalUsableSize),
-      Show(mTwiceReported.mNumBlocks, gBuf2, kBufLen, aShowTilde),
-      Percent(mTwiceReported.mNumBlocks, mTotalNumBlocks));
-  }
-
-  virtual void PrintStats(const Writer& aWriter) const
-  {
-    size_t unreportedSize =
-      mUnreported.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
-    W("    Unreported table:     %10s bytes (%s entries, %s used)\n",
-      Show(unreportedSize,                      gBuf1, kBufLen),
-      Show(mUnreported.mRecordTable.capacity(), gBuf2, kBufLen),
-      Show(mUnreported.mRecordTable.count(),    gBuf3, kBufLen));
-
-    size_t onceReportedSize =
-      mOnceReported.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
-    W("    Once-reported table:  %10s bytes (%s entries, %s used)\n",
-      Show(onceReportedSize,                      gBuf1, kBufLen),
-      Show(mOnceReported.mRecordTable.capacity(), gBuf2, kBufLen),
-      Show(mOnceReported.mRecordTable.count(),    gBuf3, kBufLen));
+  return b;
+}
 
-    size_t twiceReportedSize =
-      mTwiceReported.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
-    W("    Twice-reported table: %10s bytes (%s entries, %s used)\n",
-      Show(twiceReportedSize,                      gBuf1, kBufLen),
-      Show(mTwiceReported.mRecordTable.capacity(), gBuf2, kBufLen),
-      Show(mTwiceReported.mRecordTable.count(),    gBuf3, kBufLen));
-  }
-};
-
-class HeapAnalyzer MOZ_FINAL : public Analyzer
+// Converts a pointer to a unique ID. Reuses the existing ID for the pointer if
+// it's been seen before.
+static const char* Id(PointerIdMap& aIdMap, uint32_t& aNextId,
+                      const void* aPtr, char* aBuf, size_t aBufLen)
 {
-  RecordKindData mLive;
-
-public:
-  HeapAnalyzer() : mLive(1024) {}
-
-  virtual const char* AnalyzeFunctionName() const { return "AnalyzeHeap"; }
-
-  virtual RecordTable* ProcessBlock(const Block& aBlock)
-  {
-    mLive.processBlock(aBlock);
-
-    return &mLive.mRecordTable;
+  uint32_t id;
+  PointerIdMap::AddPtr p = aIdMap.lookupForAdd(aPtr);
+  if (!p) {
+    id = aNextId++;
+    (void)aIdMap.add(p, aPtr, id);
+  } else {
+    id = p->value();
   }
-
-  virtual void PrintRecords(const Writer& aWriter,
-                            CodeAddressService* aLocService) const
-  {
-    size_t totalUsableSize = mLive.mUsableSize;
-    PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
-                       "Live", "live", mLive.mRecordTable, totalUsableSize,
-                       mLive.mUsableSize,
-                       /* showReportedAt = */ false,
-                       /* showCategoryPercentage = */ false);
-  }
-
-  virtual void PrintSummary(const Writer& aWriter, bool aShowTilde) const
-  {
-    W("  Total: %s bytes in %s blocks\n",
-      Show(mLive.mUsableSize, gBuf1, kBufLen, aShowTilde),
-      Show(mLive.mNumBlocks,  gBuf2, kBufLen, aShowTilde));
-  }
-
-  virtual void PrintStats(const Writer& aWriter) const
-  {
-    size_t liveSize = mLive.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
-    W("    Live table:           %10s bytes (%s entries, %s used)\n",
-      Show(liveSize,                      gBuf1, kBufLen),
-      Show(mLive.mRecordTable.capacity(), gBuf2, kBufLen),
-      Show(mLive.mRecordTable.count(),    gBuf3, kBufLen));
-  }
-};
+  return Base32(id, aBuf, aBufLen);
+}
 
 static void
-AnalyzeImpl(Analyzer *aAnalyzer, const Writer& aWriter)
+AnalyzeReportsImpl(JSONWriter& aWriter)
 {
   if (!gIsDMDRunning) {
     return;
   }
 
   AutoBlockIntercepts block(Thread::Fetch());
   AutoLockState lock;
 
-  static int analysisCount = 1;
-  StatusMsg("%s %d {\n", aAnalyzer->AnalyzeFunctionName(), analysisCount++);
+  // Allocate this on the heap instead of the stack because it's fairly large.
+  auto locService = InfallibleAllocPolicy::new_<CodeAddressService>();
 
-  StatusMsg("  gathering heap block records...\n");
+  StackTraceSet usedStackTraces;
+  usedStackTraces.init(512);
+
+  PointerSet usedPcs;
+  usedPcs.init(512);
 
-  bool anyBlocksSampled = false;
+  PointerIdMap idMap;
+  idMap.init(512);
 
-  for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) {
-    const Block& b = r.front();
-    RecordTable* table = aAnalyzer->ProcessBlock(b);
+  static int analysisCount = 1;
+  StatusMsg("Dump %d {\n", analysisCount++);
+
+  aWriter.Start();
+  {
+    #define ID(p) Id(idMap, id, p, idBuf, idBufLen)
 
-    RecordKey key(b);
-    RecordTable::AddPtr p = table->lookupForAdd(key);
-    if (!p) {
-      Record tr(key);
-      (void)table->add(p, tr);
+    aWriter.IntProperty("version", kOutputVersionNumber);
+
+    aWriter.StartObjectProperty("invocation");
+    {
+      aWriter.StringProperty("dmdEnvVar", gOptions->DMDEnvVar());
+      aWriter.IntProperty("sampleBelowSize", gOptions->SampleBelowSize());
     }
-    p->Add(b);
+    aWriter.EndObject();
+
+    StatusMsg("  Constructing the heap block list...\n");
 
-    anyBlocksSampled = anyBlocksSampled || b.IsSampled();
-  }
+    static const size_t idBufLen = 16;
+    char idBuf[idBufLen];
+    uint32_t id = 0;
+
+    aWriter.StartArrayProperty("blockList");
+    {
+      for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) {
+        const Block& b = r.front();
+        b.AddStackTracesToTable(usedStackTraces);
 
-  WriteSeparator();
-  W("Invocation {\n");
-  W("  $DMD = '%s'\n", gOptions->DMDEnvVar());
-  W("  Function = %s\n", aAnalyzer->AnalyzeFunctionName());
-  W("  Sample-below size = %lld\n", (long long)(gOptions->SampleBelowSize()));
-  W("}\n\n");
+        aWriter.StartObjectElement(aWriter.SingleLineStyle);
+        {
+          if (!b.IsSampled()) {
+            aWriter.IntProperty("req", b.ReqSize());
+            if (b.SlopSize() > 0) {
+              aWriter.IntProperty("slop", b.SlopSize());
+            }
+          }
+          aWriter.StringProperty("alloc", ID(b.AllocStackTrace()));
+          if (b.NumReports() > 0) {
+            aWriter.StartArrayProperty("reps");
+            {
+              if (b.ReportStackTrace1()) {
+                aWriter.StringElement(ID(b.ReportStackTrace1()));
+              }
+              if (b.ReportStackTrace2()) {
+                aWriter.StringElement(ID(b.ReportStackTrace2()));
+              }
+            }
+            aWriter.EndArray();
+          }
+        }
+        aWriter.EndObject();
+      }
+    }
+    aWriter.EndArray();
 
-  // Allocate this on the heap instead of the stack because it's fairly large.
-  CodeAddressService* locService = InfallibleAllocPolicy::new_<CodeAddressService>();
+    StatusMsg("  Constructing the stack trace table...\n");
 
-  aAnalyzer->PrintRecords(aWriter, locService);
+    aWriter.StartObjectProperty("traceTable");
+    {
+      for (StackTraceSet::Enum e(usedStackTraces); !e.empty(); e.popFront()) {
+        const StackTrace* const st = e.front();
+        aWriter.StartArrayProperty(ID(st), aWriter.SingleLineStyle);
+        {
+          for (uint32_t i = 0; i < st->Length(); i++) {
+            const void* pc = st->Pc(i);
+            aWriter.StringElement(ID(pc));
+            usedPcs.put(pc);
+          }
+        }
+        aWriter.EndArray();
+      }
+    }
+    aWriter.EndObject();
 
-  WriteSeparator();
-  W("Summary {\n");
+    StatusMsg("  Constructing the stack frame table...\n");
 
-  bool showTilde = anyBlocksSampled;
-  aAnalyzer->PrintSummary(aWriter, showTilde);
+    aWriter.StartObjectProperty("frameTable");
+    {
+      static const size_t locBufLen = 1024;
+      char locBuf[locBufLen];
+
+      for (PointerSet::Enum e(usedPcs); !e.empty(); e.popFront()) {
+        const void* const pc = e.front();
 
-  W("}\n\n");
+        // Use 0 for the frame number. See the JSON format description comment
+        // in DMD.h to understand why.
+        locService->GetLocation(0, pc, locBuf, locBufLen);
+        aWriter.StringProperty(ID(pc), locBuf);
+      }
+    }
+    aWriter.EndObject();
 
-  // Stats are non-deterministic, so don't show them in test mode.
-  if (!gOptions->IsTestMode()) {
+    #undef ID
+  }
+  aWriter.End();
+
+  if (gOptions->ShowDumpStats()) {
     Sizes sizes;
     SizeOfInternal(&sizes);
 
-    WriteSeparator();
-    W("Execution measurements {\n");
+    static const size_t kBufLen = 64;
+    char buf1[kBufLen];
+    char buf2[kBufLen];
+    char buf3[kBufLen];
 
-    W("  Data structures that persist after Dump() ends {\n");
+    StatusMsg("  Execution measurements {\n");
 
-    W("    Used stack traces:    %10s bytes\n",
-      Show(sizes.mStackTracesUsed, gBuf1, kBufLen));
+    StatusMsg("    Data structures that persist after Dump() ends {\n");
 
-    W("    Unused stack traces:  %10s bytes\n",
-      Show(sizes.mStackTracesUnused, gBuf1, kBufLen));
+    StatusMsg("      Used stack traces:    %10s bytes\n",
+      Show(sizes.mStackTracesUsed, buf1, kBufLen));
 
-    W("    Stack trace table:    %10s bytes (%s entries, %s used)\n",
-      Show(sizes.mStackTraceTable,       gBuf1, kBufLen),
-      Show(gStackTraceTable->capacity(), gBuf2, kBufLen),
-      Show(gStackTraceTable->count(),    gBuf3, kBufLen));
+    StatusMsg("      Unused stack traces:  %10s bytes\n",
+      Show(sizes.mStackTracesUnused, buf1, kBufLen));
+
+    StatusMsg("      Stack trace table:    %10s bytes (%s entries, %s used)\n",
+      Show(sizes.mStackTraceTable,       buf1, kBufLen),
+      Show(gStackTraceTable->capacity(), buf2, kBufLen),
+      Show(gStackTraceTable->count(),    buf3, kBufLen));
 
-    W("    Block table:          %10s bytes (%s entries, %s used)\n",
-      Show(sizes.mBlockTable,       gBuf1, kBufLen),
-      Show(gBlockTable->capacity(), gBuf2, kBufLen),
-      Show(gBlockTable->count(),    gBuf3, kBufLen));
+    StatusMsg("      Block table:          %10s bytes (%s entries, %s used)\n",
+      Show(sizes.mBlockTable,       buf1, kBufLen),
+      Show(gBlockTable->capacity(), buf2, kBufLen),
+      Show(gBlockTable->count(),    buf3, kBufLen));
 
-    W("  }\n");
-    W("  Data structures that are destroyed after Dump() ends {\n");
+    StatusMsg("    }\n");
+    StatusMsg("    Data structures that are destroyed after Dump() ends {\n");
 
-    aAnalyzer->PrintStats(aWriter);
+    StatusMsg("      Location service:      %10s bytes\n",
+      Show(locService->SizeOfIncludingThis(MallocSizeOf), buf1, kBufLen));
+    StatusMsg("      Used stack traces set: %10s bytes\n",
+      Show(usedStackTraces.sizeOfExcludingThis(MallocSizeOf), buf1, kBufLen));
+    StatusMsg("      Used PCs set:          %10s bytes\n",
+      Show(usedPcs.sizeOfExcludingThis(MallocSizeOf), buf1, kBufLen));
+    StatusMsg("      Pointer ID map:        %10s bytes\n",
+      Show(idMap.sizeOfExcludingThis(MallocSizeOf), buf1, kBufLen));
 
-    W("    Location service:     %10s bytes\n",
-      Show(locService->SizeOfIncludingThis(MallocSizeOf), gBuf1, kBufLen));
-
-    W("  }\n");
-    W("  Counts {\n");
+    StatusMsg("    }\n");
+    StatusMsg("    Counts {\n");
 
     size_t hits   = locService->NumCacheHits();
     size_t misses = locService->NumCacheMisses();
     size_t requests = hits + misses;
-    W("    Location service:    %10s requests\n",
-      Show(requests, gBuf1, kBufLen));
+    StatusMsg("      Location service:    %10s requests\n",
+      Show(requests, buf1, kBufLen));
 
     size_t count    = locService->CacheCount();
     size_t capacity = locService->CacheCapacity();
-    W("    Location service cache:  "
+    StatusMsg("      Location service cache:  "
       "%4.1f%% hit rate, %.1f%% occupancy at end\n",
       Percent(hits, requests), Percent(count, capacity));
 
-    W("  }\n");
-    W("}\n\n");
+    StatusMsg("    }\n");
+    StatusMsg("  }\n");
   }
 
   InfallibleAllocPolicy::delete_(locService);
 
   StatusMsg("}\n");
 }
 
 MOZ_EXPORT void
-AnalyzeReports(const Writer& aWriter)
+AnalyzeReports(JSONWriter& aWriter)
 {
-  ReportsAnalyzer aAnalyzer;
-  AnalyzeImpl(&aAnalyzer, aWriter);
-}
-
-MOZ_EXPORT void
-AnalyzeHeap(const Writer& aWriter)
-{
-  HeapAnalyzer analyzer;
-  AnalyzeImpl(&analyzer, aWriter);
+  AnalyzeReportsImpl(aWriter);
+  ClearReports();
 }
 
 //---------------------------------------------------------------------------
 // Testing
 //---------------------------------------------------------------------------
 
 // This function checks that heap blocks that have the same stack trace but
 // different (or no) reporters get aggregated separately.
@@ -2194,30 +1868,33 @@ void foo()
 static void
 UseItOrLoseIt(void* a)
 {
   char buf[64];
   sprintf(buf, "%p\n", a);
   fwrite(buf, 1, strlen(buf) + 1, stderr);
 }
 
-// The output from this should be compared against test-expected.dmd.  It's
-// been tested on Linux64, and probably will give different results on other
+// The output from this should be tested with check_test_output.py.  It's been
+// tested on Linux64, and probably will give different results on other
 // platforms.
 static void
-RunTestMode(FILE* fp)
+RunTestMode(UniquePtr<FpWriteFunc> aF1, UniquePtr<FpWriteFunc> aF2,
+            UniquePtr<FpWriteFunc> aF3, UniquePtr<FpWriteFunc> aF4)
 {
-  Writer writer(FpWrite, fp);
-
   // The first part of this test requires sampling to be disabled.
   gOptions->SetSampleBelowSize(1);
 
+  //---------
+
   // AnalyzeReports 1.  Zero for everything.
-  AnalyzeReports(writer);
-  AnalyzeHeap(writer);
+  JSONWriter writer1(Move(aF1));
+  AnalyzeReports(writer1);
+
+  //---------
 
   // AnalyzeReports 2: 1 freed, 9 out of 10 unreported.
   // AnalyzeReports 3: still present and unreported.
   int i;
   char* a;
   for (i = 0; i < 10; i++) {
       a = (char*) malloc(100);
       UseItOrLoseIt(a);
@@ -2321,34 +1998,34 @@ RunTestMode(FILE* fp)
 //posix_memalign(&y, 128, 129);         // rounds up to 256
 //UseItOrLoseIt(y);
   // XXX: valloc doesn't work on Windows.
 //void* z = valloc(1);                  // rounds up to 4096
 //UseItOrLoseIt(z);
 //aligned_alloc(64, 256);               // XXX: C11 only
 
   // AnalyzeReports 2.
-  AnalyzeReports(writer);
-  AnalyzeHeap(writer);
+  JSONWriter writer2(Move(aF2));
+  AnalyzeReports(writer2);
 
   //---------
 
   Report(a2);
   Report(a2);
   free(c);
   free(e);
   Report(e2);
   free(e3);
 //free(x);
 //free(y);
 //free(z);
 
   // AnalyzeReports 3.
-  AnalyzeReports(writer);
-  AnalyzeHeap(writer);
+  JSONWriter writer3(Move(aF3));
+  AnalyzeReports(writer3);
 
   //---------
 
   // Clear all knowledge of existing blocks to give us a clean slate.
   gBlockTable->clear();
 
   gOptions->SetSampleBelowSize(128);
 
@@ -2402,18 +2079,18 @@ RunTestMode(FILE* fp)
     UseItOrLoseIt(s);
   }
   MOZ_ASSERT(gSmallBlockActualSizeCounter == 64);
 
   // At the end we're 64 bytes into the current sample so we report ~1,424
   // bytes of allocation overall, which is 64 less than the real value 1,488.
 
   // AnalyzeReports 4.
-  AnalyzeReports(writer);
-  AnalyzeHeap(writer);
+  JSONWriter writer4(Move(aF4));
+  AnalyzeReports(writer4);
 }
 
 //---------------------------------------------------------------------------
 // Stress testing microbenchmark
 //---------------------------------------------------------------------------
 
 // This stops otherwise-unused variables from being optimized away.
 static void
@@ -2469,19 +2146,19 @@ stress1()
 // This stress test does lots of allocations and frees, which is where most of
 // DMD's overhead occurs.  It allocates 1,000,000 64-byte blocks, spread evenly
 // across 1,000 distinct stack traces.  It frees every second one immediately
 // after allocating it.
 //
 // It's highly artificial, but it's deterministic and easy to run.  It can be
 // timed under different conditions to glean performance data.
 static void
-RunStressMode(FILE* fp)
+RunStressMode(UniquePtr<FpWriteFunc> aF)
 {
-  Writer writer(FpWrite, fp);
+  JSONWriter writer(Move(aF));
 
   // Disable sampling for maximum stress.
   gOptions->SetSampleBelowSize(1);
 
   stress1(); stress1(); stress1(); stress1(); stress1();
   stress1(); stress1(); stress1(); stress1(); stress1();
 
   AnalyzeReports(writer);
--- a/memory/replace/dmd/DMD.h
+++ b/memory/replace/dmd/DMD.h
@@ -2,76 +2,131 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DMD_h___
 #define DMD_h___
 
-#include <stdarg.h>
 #include <string.h>
 
 #include "mozilla/Types.h"
 
 namespace mozilla {
+
+class JSONWriter;
+
 namespace dmd {
 
 // Mark a heap block as reported by a memory reporter.
 MOZ_EXPORT void
 Report(const void* aPtr);
 
 // Mark a heap block as reported immediately on allocation.
 MOZ_EXPORT void
 ReportOnAlloc(const void* aPtr);
 
-class Writer
-{
-public:
-  typedef void (*WriterFun)(void* aWriteState, const char* aFmt, va_list aAp);
-
-  Writer(WriterFun aWriterFun, void* aWriteState)
-    : mWriterFun(aWriterFun), mWriteState(aWriteState)
-  {}
-
-  void Write(const char* aFmt, ...) const;
-
-private:
-  WriterFun mWriterFun;
-  void*     mWriteState;
-};
-
 // Clears existing reportedness data from any prior runs of the memory
 // reporters.  The following sequence should be used.
 // - ClearReports()
 // - run the memory reporters
 // - AnalyzeReports()
 // This sequence avoids spurious twice-reported warnings.
 MOZ_EXPORT void
 ClearReports();
 
-// Determines which heap blocks have been reported, and dumps a human-readable
-// summary (via |aWrite|).  If |aWrite| is nullptr it will dump to stderr.
-// Beware: this output may have very long lines.
-MOZ_EXPORT void
-AnalyzeReports(const Writer& aWriter);
-
-// Measures all heap blocks, and dumps a human-readable summary (via |aWrite|).
-// If |aWrite| is nullptr it will dump to stderr.  Beware: this output may
-// have very long lines.
+// Determines which heap blocks have been reported, and dumps JSON output
+// (via |aWriter|) describing the heap.
+//
+// The following sample output contains comments that explain the format and
+// design choices. The output files can be quite large, so a number of
+// decisions were made to minimize size, such as using short property names and
+// omitting properties whenever possible.
+//
+// {
+//   // The version number of the format, which will be incremented each time
+//   // backwards-incompatible changes are made. A mandatory integer.
+//   "version": 1,
+//
+//   // Information about how DMD was invoked. A mandatory object.
+//   "invocation": {
+//     // The contents of the $DMD environment variable. A mandatory string.
+//     "dmdEnvVar": "1",
+//
+//     // The value of the --sample-below-size option. A mandatory integer.
+//     "sampleBelowSize": 4093
+//   },
+//
+//   // Details of all analyzed heap blocks. A mandatory array.
+//   "blockList": [
+//     // An example of a non-sampled heap block.
+//     {
+//       // Requested size, in bytes. In non-sampled blocks this is a
+//       // mandatory integer. In sampled blocks this is not present, and the
+//       // requested size is equal to the "sampleBelowSize" value. Therefore,
+//       // the block is sampled if and only if this property is absent.
+//       "req": 3584,
+//
+//       // Requested slop size, in bytes. This is mandatory if it is non-zero,
+//       // but omitted otherwise. Because sampled blocks never have slop, this
+//       // property is never present for non-sampled blocks.
+//       "slop": 512,
+//
+//       // The stack trace at which the block was allocated. A mandatory
+//       // string which indexes into the "traceTable" object.
+//       "alloc": "A"
+//     },
+//
+//     // An example of a sampled heap block.
+//     {
+//       "alloc": "B",
+//
+//       // One or more stack traces at which this heap block was reported by a
+//       // memory reporter. An optional array. The elements are strings that
+//       // index into the "traceTable" object.
+//       "reps": ["C"]
+//     }
+//   ],
+//
+//   // The stack traces referenced by elements of the "blockList" array. This
+//   // could be an array, but making it an object makes it easier to see
+//   // which stacks correspond to which references in the "blockList" array.
+//   "traceTable": {
+//     // Each property corresponds to a stack trace mentioned in the "blocks"
+//     // object. Each element is an index into the "frameTable" object.
+//     "A": ["D", "E"],
+//     "B": ["D", "F"],
+//     "C": ["G", "H"]
+//   },
+//
+//   // The stack frames referenced by the "traceTable" object. The
+//   // descriptions can be quite long, so they are stored separately from the
+//   // "traceTable" object so that each one only has to be written once.
+//   // This could also be an array, but again, making it an object makes it
+//   // easier to see which frames correspond to which references in the
+//   // "traceTable" object.
+//   "frameTable": {
+//     // Each property key is a frame key mentioned in the "traceTable" object.
+//     // Each property value is a string containing a frame description. Each
+//     // frame description must be in a format recognized by the stack-fixing
+//     // scripts (e.g. fix_linux_stack.py), which require a frame number at
+//     // the start. Because each stack frame description in this table can
+//     // be shared between multiple stack traces, we use a dummy value of
+//     // #00. The proper frame number can be reconstructed later by scripts
+//     // that output stack traces in a conventional non-shared format.
+//     "D": "#00: foo (Foo.cpp:123)",
+//     "E": "#00: bar (Bar.cpp:234)",
+//     "F": "#00: baz (Baz.cpp:345)",
+//     "G": "#00: quux (Quux.cpp:456)",
+//     "H": "#00: quuux (Quux.cpp:567)"
+//   }
+// }
 MOZ_EXPORT void
-AnalyzeHeap(const Writer& aWriter);
-
-// A useful |WriterFun|.  For example, if |fp| is a FILE* you want
-// |AnalyzeReports|'s output to be written to, call:
-//
-//   dmd::Writer writer(FpWrite, fp);
-//   dmd::AnalyzeReports(writer);
-MOZ_EXPORT void
-FpWrite(void* aFp, const char* aFmt, va_list aAp);
+AnalyzeReports(mozilla::JSONWriter& aWriter);
 
 struct Sizes
 {
   size_t mStackTracesUsed;
   size_t mStackTracesUnused;
   size_t mStackTraceTable;
   size_t mBlockTable;
 
--- a/memory/replace/dmd/check_test_output.py
+++ b/memory/replace/dmd/check_test_output.py
@@ -1,14 +1,18 @@
 #! /usr/bin/python
+#
+# 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/.
 
 """This script takes the file produced by DMD's test mode and checks its
 correctness.
 
-It produces the following output files: $TMP/test-{fixed,filtered,diff}.dmd.
+It produces the following output files: $TMP/full-{fixed,filtered,diff}.dmd.
 
 It runs the appropriate fix* script to get nice stack traces.  It also
 filters out platform-specific details from the test output file.
 
 Note: you must run this from the same directory that you invoked DMD's test
 mode, otherwise the fix* script will not work properly, because some of the
 paths in the test output are relative.
 
@@ -18,63 +22,55 @@ from __future__ import print_function
 
 import os
 import platform
 import re
 import subprocess
 import sys
 import tempfile
 
-
-def main():
-
-    # Arguments
-
-    if (len(sys.argv) != 3):
-        print("usage:", sys.argv[0], "<topsrcdir> <test.dmd>")
-        sys.exit(1)
-
-    srcdir = sys.argv[1]
-
+def test(src_dir, kind, options, i):
     # Filenames
-
-    tempdir = tempfile.gettempdir()
-    in_name       = sys.argv[2]
-    fixed_name    = tempdir + os.sep + "test-fixed.dmd"
-    filtered_name = tempdir + os.sep + "test-filtered.dmd"
-    diff_name     = tempdir + os.sep + "test-diff.dmd"
-    expected_name = srcdir + os.sep + \
-                    "memory/replace/dmd/test-expected.dmd"
+    tmp_dir = tempfile.gettempdir()
+    in_name        = os.path.join(src_dir, "full{:d}.json".format(i))
+    fixed_name     = os.path.join(tmp_dir, "full-{:}-fixed{:d}.json".format(kind, i))
+    converted_name = os.path.join(tmp_dir, "full-{:}-converted{:d}.txt".format(kind, i))
+    filtered_name  = os.path.join(tmp_dir, "full-{:}-filtered{:d}.txt".format(kind, i))
+    diff_name      = os.path.join(tmp_dir, "full-{:}-diff{:d}.txt".format(kind, i))
+    expected_name  = os.path.join(src_dir, "memory", "replace", "dmd", "test", "full-{:}-expected{:d}.txt".format(kind, i))
 
     # Fix stack traces
 
-    print("fixing output to", fixed_name)
-
-    sysname = platform.system()
-    if sysname == "Linux":
-        fix = srcdir + os.sep + "tools/rb/fix_linux_stack.py"
-    elif sysname == "Darwin":
-        fix = srcdir + os.sep + "tools/rb/fix_macosx_stack.py"
+    sys_name = platform.system()
+    fix = os.path.join(src_dir, "tools", "rb")
+    if sys_name == "Linux":
+        fix = os.path.join(fix, "fix_linux_stack.py")
+    elif sys_name == "Darwin":
+        fix = os.path.join(fix, "fix_macosx_stack.py")
     else:
-        print("unhandled platform: " + sysname, file=sys.stderr)
+        print("unhandled platform: " + sys_name, file=sys.stderr)
         sys.exit(1)
 
     subprocess.call(fix, stdin=open(in_name, "r"),
                          stdout=open(fixed_name, "w"))
 
+    # Convert from JSON
+
+    convert = [os.path.join(src_dir, "memory", "replace", "dmd", "dmd.py")] + \
+               options + ['--no-fix-stacks', fixed_name]
+    subprocess.call(convert, stdout=open(converted_name, "w"))
+
     # Filter output
 
     # In heap block records we filter out most stack frames.  The only thing
     # we leave behind is a "DMD.cpp" entry if we see one or more frames that
     # have DMD.cpp in them.  There is simply too much variation to do anything
     # better than that.
 
-    print("filtering output to", filtered_name)
-
-    with open(fixed_name, "r") as fin, \
+    with open(converted_name, "r") as fin, \
          open(filtered_name, "w") as fout:
 
         test_frame_re = re.compile(r".*(DMD.cpp)")
 
         for line in fin:
             if re.match(r"  (Allocated at {|Reported( again)? at {)", line):
                 # It's a heap block record.
                 print(line, end='', file=fout)
@@ -95,22 +91,37 @@ def main():
                         break
 
             else:
                 # A line that needs no special handling.  Copy it through.
                 print(line, end='', file=fout)
 
     # Compare with expected output
 
-    print("diffing output to", diff_name)
-
     ret = subprocess.call(["diff", "-u", expected_name, filtered_name],
                           stdout=open(diff_name, "w"))
 
     if ret == 0:
-        print("test PASSED")
+        print("TEST-PASS | {:} {:d} | ok".format(kind, i))
     else:
-        print("test FAILED (did you remember to run this script and Firefox "
-              "in the same directory?)")
+        print("TEST-UNEXPECTED-FAIL | {:} {:d} | mismatch".format(kind, i))
+        print("Output files:")
+        print("- " + fixed_name);
+        print("- " + converted_name);
+        print("- " + filtered_name);
+        print("- " + diff_name);
+
+
+def main():
+    if (len(sys.argv) != 2):
+        print("usage:", sys.argv[0], "<topsrcdir>")
+        sys.exit(1)
+
+    src_dir = sys.argv[1]
+
+    ntests = 4
+    for i in range(1, ntests+1):
+        test(src_dir, "reports", [], i)
+        test(src_dir, "heap", ["--ignore-reports"], i)
 
 
 if __name__ == "__main__":
     main()
new file mode 100755
--- /dev/null
+++ b/memory/replace/dmd/dmd.py
@@ -0,0 +1,451 @@
+#! /usr/bin/python
+#
+# 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/.
+
+'''This script analyzes a JSON file emitted by DMD.'''
+
+from __future__ import print_function, division
+
+import argparse
+import collections
+import json
+import os
+import platform
+import re
+import shutil
+import sys
+import tempfile
+
+# The DMD output version this script handles.
+outputVersion = 1
+
+# If --ignore-alloc-fns is specified, stack frames containing functions that
+# match these strings will be removed.
+allocatorFns = [
+    'replace_malloc',
+    'replace_calloc',
+    'replace_realloc',
+    'replace_memalign',
+    'replace_posix_memalign',
+    'moz_xmalloc',
+    'moz_xcalloc',
+    'moz_xrealloc',
+    'operator new(',
+    'operator new[](',
+    'g_malloc',
+    'g_slice_alloc',
+    'callocCanGC',
+    'reallocCanGC',
+    'vpx_malloc',
+    'vpx_calloc',
+    'vpx_realloc',
+    'vpx_memalign',
+    'js_malloc',
+    'js_calloc',
+    'js_realloc',
+    'pod_malloc',
+    'pod_calloc',
+    'pod_realloc',
+]
+
+class Record(object):
+    def __init__(self):
+        self.numBlocks = 0
+        self.reqSize = 0
+        self.slopSize = 0
+        self.usableSize = 0
+        self.isSampled = False
+        self.usableSizes = collections.defaultdict(int)
+
+    @staticmethod
+    def cmpByIsSampled(r1, r2):
+        # Treat sampled as smaller than non-sampled.
+        return cmp(r2.isSampled, r1.isSampled)
+
+    @staticmethod
+    def cmpByUsableSize(r1, r2):
+        # Sort by usable size, then req size, then by isSampled.
+        return cmp(r1.usableSize, r2.usableSize) or Record.cmpByReqSize(r1, r2)
+
+    @staticmethod
+    def cmpByReqSize(r1, r2):
+        # Sort by req size, then by isSampled.
+        return cmp(r1.reqSize, r2.reqSize) or Record.cmpByIsSampled(r1, r2)
+
+    @staticmethod
+    def cmpBySlopSize(r1, r2):
+        # Sort by slop size, then by isSampled.
+        return cmp(r1.slopSize, r2.slopSize) or Record.cmpByIsSampled(r1, r2)
+
+
+sortByChoices = {
+    'usable': Record.cmpByUsableSize,   # the default
+    'req':    Record.cmpByReqSize,
+    'slop':   Record.cmpBySlopSize,
+}
+
+
+def parseCommandLine():
+    # 24 is the maximum number of frames that DMD will produce.
+    def range_1_24(string):
+        value = int(string)
+        if value < 1 or value > 24:
+            msg = '{:s} is not in the range 1..24'.format(string)
+            raise argparse.ArgumentTypeError(msg)
+        return value
+
+    description = '''
+Analyze heap data produced by DMD.
+If no files are specified, read from stdin.
+Write to stdout unless -o/--output is specified.
+Stack traces are fixed to show function names, filenames and line numbers
+unless --no-fix-stacks is specified; stack fixing modifies the original file
+and may take some time.
+'''
+    p = argparse.ArgumentParser(description=description)
+
+    p.add_argument('-o', '--output', type=argparse.FileType('w'),
+                   help='output file; stdout if unspecified')
+
+    p.add_argument('-f', '--max-frames', type=range_1_24,
+                   help='maximum number of frames to consider in each trace')
+
+    p.add_argument('-r', '--ignore-reports', action='store_true',
+                   help='ignore memory reports data; useful if you just ' +
+                        'want basic heap profiling')
+
+    p.add_argument('-s', '--sort-by', choices=sortByChoices.keys(),
+                   default=sortByChoices.keys()[0],
+                   help='sort the records by a particular metric')
+
+    p.add_argument('-a', '--ignore-alloc-fns', action='store_true',
+                   help='ignore allocation functions at the start of traces')
+
+    p.add_argument('-b', '--show-all-block-sizes', action='store_true',
+                   help='show individual block sizes for each record')
+
+    p.add_argument('--no-fix-stacks', action='store_true',
+                   help='do not fix stacks')
+
+    p.add_argument('input_file', type=argparse.FileType('r'))
+
+    return p.parse_args(sys.argv[1:])
+
+
+# Fix stacks if necessary: first write the output to a tempfile, then replace
+# the original file with it.
+def fixStackTraces(args):
+    # This append() call is needed to make the import statements work when this
+    # script is installed as a symlink.
+    sys.path.append(os.path.dirname(__file__))
+
+    # XXX: should incorporate fix_stack_using_bpsyms.py here as well, like in
+    #      testing/mochitests/runtests.py
+    sysname = platform.system()
+    if sysname == 'Linux':
+        import fix_linux_stack as fixModule
+        fix = lambda line: fixModule.fixSymbols(line)
+    elif sysname == 'Darwin':
+        import fix_macosx_stack as fixModule
+        fix = lambda line: fixModule.fixSymbols(line)
+    else:
+        fix = None  # there is no fix script for Windows
+
+    if fix:
+        # Fix stacks, writing output to a temporary file, and then
+        # overwrite the original file.
+        with tempfile.NamedTemporaryFile(delete=False) as tmp:
+            for line in args.input_file:
+                tmp.write(fix(line))
+            shutil.move(tmp.name, args.input_file.name)
+
+        args.input_file = open(args.input_file.name)
+
+
+def main():
+    args = parseCommandLine()
+
+    # Fix stack traces unless otherwise instructed.
+    if not args.no_fix_stacks:
+        fixStackTraces(args)
+
+    j = json.load(args.input_file)
+
+    if j['version'] != outputVersion:
+        raise Exception("'version' property isn't '{:d}'".format(outputVersion))
+
+    # Extract the main parts of the JSON object.
+    invocation = j['invocation']
+    dmdEnvVar = invocation['dmdEnvVar']
+    sampleBelowSize = invocation['sampleBelowSize']
+    blockList = j['blockList']
+    traceTable = j['traceTable']
+    frameTable = j['frameTable']
+
+    heapIsSampled = sampleBelowSize > 1     # is sampling present?
+
+    # Remove allocation functions at the start of traces.
+    if args.ignore_alloc_fns:
+        # Build a regexp that matches every function in allocatorFns.
+        escapedAllocatorFns = map(re.escape, allocatorFns)
+        fn_re = re.compile('|'.join(escapedAllocatorFns))
+
+        # Remove allocator fns from each stack trace.
+        for traceKey, frameKeys in traceTable.items():
+            numSkippedFrames = 0
+            for frameKey in frameKeys:
+                frameDesc = frameTable[frameKey]
+                if re.search(fn_re, frameDesc):
+                    numSkippedFrames += 1
+                else:
+                    break
+            if numSkippedFrames > 0:
+                traceTable[traceKey] = frameKeys[numSkippedFrames:]
+
+    # Trim the number of frames.
+    for traceKey, frameKeys in traceTable.items():
+        if len(frameKeys) > args.max_frames:
+            traceTable[traceKey] = frameKeys[:args.max_frames]
+
+    # Aggregate blocks into records. All sufficiently similar blocks go into a
+    # single record.
+
+    if args.ignore_reports:
+        liveRecords = collections.defaultdict(Record)
+    else:
+        unreportedRecords    = collections.defaultdict(Record)
+        onceReportedRecords  = collections.defaultdict(Record)
+        twiceReportedRecords = collections.defaultdict(Record)
+
+    heapUsableSize = 0
+    heapBlocks = 0
+
+    for block in blockList:
+        # For each block we compute a |recordKey|, and all blocks with the same
+        # |recordKey| are aggregated into a single record. The |recordKey| is
+        # derived from the block's 'alloc' and 'reps' (if present) stack
+        # traces.
+        #
+        # Each stack trace has a key in the JSON file. But we don't use that
+        # key to construct |recordKey|; instead we use the frame keys.
+        # This is because the stack trimming done for --max-frames can cause
+        # stack traces with distinct trace keys to end up with the same frame
+        # keys, and these should be considered equivalent. E.g. if we have
+        # distinct traces T1:[A,B,C] and T2:[A,B,D] and we trim the final frame
+        # of each they should be considered equivalent.
+        allocatedAt = block['alloc']
+        if args.ignore_reports:
+            recordKey = str(traceTable[allocatedAt])
+            records = liveRecords
+        else:
+            recordKey = str(traceTable[allocatedAt])
+            if 'reps' in block:
+                reportedAts = block['reps']
+                for reportedAt in reportedAts:
+                    recordKey += str(traceTable[reportedAt])
+                if len(reportedAts) == 1:
+                    records = onceReportedRecords
+                else:
+                    records = twiceReportedRecords
+            else:
+                records = unreportedRecords
+
+        record = records[recordKey]
+
+        if 'req' in block:
+            # not sampled
+            reqSize = block['req']
+            slopSize = block.get('slop', 0)
+            isSampled = False
+        else:
+            # sampled
+            reqSize = sampleBelowSize
+            if 'slop' in block:
+                raise Exception("'slop' property in sampled block'")
+            slopSize = 0
+            isSampled = True
+
+        usableSize = reqSize + slopSize
+        heapUsableSize += usableSize
+        heapBlocks += 1
+
+        record.numBlocks  += 1
+        record.reqSize    += reqSize
+        record.slopSize   += slopSize
+        record.usableSize += usableSize
+        record.isSampled   = record.isSampled or isSampled
+        record.allocatedAt = block['alloc']
+        if args.ignore_reports:
+            pass
+        else:
+            if 'reps' in block:
+                record.reportedAts = block['reps']
+        record.usableSizes[(usableSize, isSampled)] += 1
+
+    # Print records.
+
+    separator = '#' + '-' * 65 + '\n'
+
+    def number(n, isSampled):
+        '''Format a number, with comma as a separator and a '~' prefix if it's
+        sampled.'''
+        return '{:}{:,d}'.format('~' if isSampled else '', n)
+
+    def perc(m, n):
+        return 0 if n == 0 else (100 * m / n)
+
+    def plural(n):
+        return '' if n == 1 else 's'
+
+    # Prints to stdout, or to file if -o/--output was specified.
+    def out(*arguments, **kwargs):
+        print(*arguments, file=args.output, **kwargs)
+
+    def printStack(traceTable, frameTable, traceKey):
+        # The frame number is always '#00' (see DMD.h for why), so we have to
+        # replace that with the correct frame number.
+        for n, frameKey in enumerate(traceTable[traceKey], start=1):
+            out('    #{:02d}{:}'.format(n, frameTable[frameKey][3:]))
+
+    def printRecords(recordKind, records, heapUsableSize):
+        RecordKind = recordKind.capitalize()
+        out(separator)
+        numRecords = len(records)
+        cmpRecords = sortByChoices[args.sort_by]
+        sortedRecords = sorted(records.values(), cmp=cmpRecords, reverse=True)
+        kindBlocks = 0
+        kindUsableSize = 0
+        maxRecord = 1000
+
+        # First iteration: get totals, etc.
+        for record in sortedRecords:
+            kindBlocks     += record.numBlocks
+            kindUsableSize += record.usableSize
+
+        # Second iteration: print.
+        if numRecords == 0:
+            out('# no {:} heap blocks\n'.format(recordKind))
+
+        kindCumulativeUsableSize = 0
+        for i, record in enumerate(sortedRecords, start=1):
+            # Stop printing at the |maxRecord|th record.
+            if i == maxRecord:
+                out('# {:}: stopping after {:,d} heap block records\n'.
+                    format(RecordKind, i))
+                break
+
+            kindCumulativeUsableSize += record.usableSize
+
+            isSampled = record.isSampled
+
+            out(RecordKind + ' {')
+            out('  {:} block{:} in heap block record {:,d} of {:,d}'.
+                format(number(record.numBlocks, isSampled),
+                       plural(record.numBlocks), i, numRecords))
+            out('  {:} bytes ({:} requested / {:} slop)'.
+                format(number(record.usableSize, isSampled),
+                       number(record.reqSize, isSampled),
+                       number(record.slopSize, isSampled)))
+            out('  {:4.2f}% of the heap ({:4.2f}% cumulative)'.
+                format(perc(record.usableSize, heapUsableSize),
+                       perc(kindCumulativeUsableSize, heapUsableSize)))
+            if args.ignore_reports:
+                pass
+            else:
+                out('  {:4.2f}% of {:} ({:4.2f}% cumulative)'.
+                    format(perc(record.usableSize, kindUsableSize),
+                           recordKind,
+                           perc(kindCumulativeUsableSize, kindUsableSize)))
+
+            if args.show_all_block_sizes:
+                usableSizes = sorted(record.usableSizes.items(), reverse=True)
+
+                out('  Individual block sizes: ', end='')
+                isFirst = True
+                for (usableSize, isSampled), count in usableSizes:
+                    if not isFirst:
+                        out('; ', end='')
+                    out('{:}'.format(number(usableSize, isSampled)), end='')
+                    if count > 1:
+                        out(' x {:,d}'.format(count), end='')
+                    isFirst = False
+                out()
+
+            out('  Allocated at {')
+            printStack(traceTable, frameTable, record.allocatedAt)
+            out('  }')
+            if args.ignore_reports:
+                pass
+            else:
+                if hasattr(record, 'reportedAts'):
+                    for n, reportedAt in enumerate(record.reportedAts):
+                        again = 'again ' if n > 0 else ''
+                        out('  Reported {:}at {{'.format(again))
+                        printStack(traceTable, frameTable, reportedAt)
+                        out('  }')
+            out('}\n')
+
+        return (kindUsableSize, kindBlocks)
+
+
+    # Print header.
+    out(separator)
+    out('Invocation {')
+    out('  $DMD = \'' + dmdEnvVar + '\'')
+    out('  Sample-below size = ' + str(sampleBelowSize))
+    out('}\n')
+
+    # Print records.
+    if args.ignore_reports:
+        liveUsableSize, liveBlocks = \
+            printRecords('live', liveRecords, heapUsableSize)
+    else:
+        twiceReportedUsableSize, twiceReportedBlocks = \
+            printRecords('twice-reported', twiceReportedRecords, heapUsableSize)
+
+        unreportedUsableSize, unreportedBlocks = \
+            printRecords('unreported',     unreportedRecords, heapUsableSize)
+
+        onceReportedUsableSize, onceReportedBlocks = \
+            printRecords('once-reported',  onceReportedRecords, heapUsableSize)
+
+    # Print summary.
+    out(separator)
+    out('Summary {')
+    if args.ignore_reports:
+        out('  Total: {:} bytes in {:} blocks'.
+            format(number(liveUsableSize, heapIsSampled),
+                   number(liveBlocks, heapIsSampled)))
+    else:
+        fmt = '  {:15} {:>12} bytes ({:6.2f}%) in {:>7} blocks ({:6.2f}%)'
+        out(fmt.
+            format('Total:',
+                   number(heapUsableSize, heapIsSampled),
+                   100,
+                   number(heapBlocks, heapIsSampled),
+                   100))
+        out(fmt.
+            format('Unreported:',
+                   number(unreportedUsableSize, heapIsSampled),
+                   perc(unreportedUsableSize, heapUsableSize),
+                   number(unreportedBlocks, heapIsSampled),
+                   perc(unreportedBlocks, heapBlocks)))
+        out(fmt.
+            format('Once-reported:',
+                   number(onceReportedUsableSize, heapIsSampled),
+                   perc(onceReportedUsableSize, heapUsableSize),
+                   number(onceReportedBlocks, heapIsSampled),
+                   perc(onceReportedBlocks, heapBlocks)))
+        out(fmt.
+            format('Twice-reported:',
+                   number(twiceReportedUsableSize, heapIsSampled),
+                   perc(twiceReportedUsableSize, heapUsableSize),
+                   number(twiceReportedBlocks, heapIsSampled),
+                   perc(twiceReportedBlocks, heapBlocks)))
+    out('}\n')
+
+
+if __name__ == '__main__':
+    main()
deleted file mode 100644
--- a/memory/replace/dmd/test-expected.dmd
+++ /dev/null
@@ -1,841 +0,0 @@
-#-----------------------------------------------------------------
-
-Invocation {
-  $DMD = '--mode=test'
-  Function = AnalyzeReports
-  Sample-below size = 1
-}
-
-#-----------------------------------------------------------------
-
-# no twice-reported heap blocks
-
-#-----------------------------------------------------------------
-
-# no unreported heap blocks
-
-#-----------------------------------------------------------------
-
-# no once-reported heap blocks
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total:                     0 bytes (100.00%) in       0 blocks (100.00%)
-  Unreported:                0 bytes (  0.00%) in       0 blocks (  0.00%)
-  Once-reported:             0 bytes (  0.00%) in       0 blocks (  0.00%)
-  Twice-reported:            0 bytes (  0.00%) in       0 blocks (  0.00%)
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
-  $DMD = '--mode=test'
-  Function = AnalyzeHeap
-  Sample-below size = 1
-}
-
-#-----------------------------------------------------------------
-
-# no live heap blocks
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total: 0 bytes in 0 blocks
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
-  $DMD = '--mode=test'
-  Function = AnalyzeReports
-  Sample-below size = 1
-}
-
-#-----------------------------------------------------------------
-
-Twice-reported {
-  1 block in heap block record 1 of 4
-  80 bytes (79 requested / 1 slop)
-  0.66% of the heap (0.66% cumulative)
-  29.41% of twice-reported (29.41% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-  Reported again at {
-    ... DMD.cpp
-  }
-}
-
-Twice-reported {
-  1 block in heap block record 2 of 4
-  80 bytes (78 requested / 2 slop)
-  0.66% of the heap (1.32% cumulative)
-  29.41% of twice-reported (58.82% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-  Reported again at {
-    ... DMD.cpp
-  }
-}
-
-Twice-reported {
-  1 block in heap block record 3 of 4
-  80 bytes (77 requested / 3 slop)
-  0.66% of the heap (1.99% cumulative)
-  29.41% of twice-reported (88.24% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-  Reported again at {
-    ... DMD.cpp
-  }
-}
-
-Twice-reported {
-  1 block in heap block record 4 of 4
-  32 bytes (30 requested / 2 slop)
-  0.26% of the heap (2.25% cumulative)
-  11.76% of twice-reported (100.00% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-  Reported again at {
-    ... DMD.cpp
-  }
-}
-
-#-----------------------------------------------------------------
-
-Unreported {
-  9 blocks in heap block record 1 of 3
-  1,008 bytes (900 requested / 108 slop)
-  8.34% of the heap (8.34% cumulative)
-  81.82% of unreported (81.82% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Unreported {
-  2 blocks in heap block record 2 of 3
-  112 bytes (112 requested / 0 slop)
-  0.93% of the heap (9.27% cumulative)
-  9.09% of unreported (90.91% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Unreported {
-  2 blocks in heap block record 3 of 3
-  112 bytes (112 requested / 0 slop)
-  0.93% of the heap (10.19% cumulative)
-  9.09% of unreported (100.00% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-#-----------------------------------------------------------------
-
-Once-reported {
-  1 block in heap block record 1 of 11
-  8,192 bytes (4,097 requested / 4,095 slop)
-  67.77% of the heap (67.77% cumulative)
-  77.40% of once-reported (77.40% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-Once-reported {
-  1 block in heap block record 2 of 11
-  1,024 bytes (1,023 requested / 1 slop)
-  8.47% of the heap (76.24% cumulative)
-  9.67% of once-reported (87.07% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-Once-reported {
-  1 block in heap block record 3 of 11
-  512 bytes (512 requested / 0 slop)
-  4.24% of the heap (80.48% cumulative)
-  4.84% of once-reported (91.91% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-Once-reported {
-  2 blocks in heap block record 4 of 11
-  240 bytes (240 requested / 0 slop)
-  1.99% of the heap (82.46% cumulative)
-  2.27% of once-reported (94.18% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-Once-reported {
-  2 blocks in heap block record 5 of 11
-  240 bytes (240 requested / 0 slop)
-  1.99% of the heap (84.45% cumulative)
-  2.27% of once-reported (96.45% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-Once-reported {
-  1 block in heap block record 6 of 11
-  96 bytes (96 requested / 0 slop)
-  0.79% of the heap (85.24% cumulative)
-  0.91% of once-reported (97.35% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-Once-reported {
-  1 block in heap block record 7 of 11
-  96 bytes (96 requested / 0 slop)
-  0.79% of the heap (86.04% cumulative)
-  0.91% of once-reported (98.26% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-Once-reported {
-  1 block in heap block record 8 of 11
-  80 bytes (80 requested / 0 slop)
-  0.66% of the heap (86.70% cumulative)
-  0.76% of once-reported (99.02% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-Once-reported {
-  1 block in heap block record 9 of 11
-  80 bytes (80 requested / 0 slop)
-  0.66% of the heap (87.36% cumulative)
-  0.76% of once-reported (99.77% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-Once-reported {
-  1 block in heap block record 10 of 11
-  16 bytes (10 requested / 6 slop)
-  0.13% of the heap (87.49% cumulative)
-  0.15% of once-reported (99.92% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-Once-reported {
-  1 block in heap block record 11 of 11
-  8 bytes (0 requested / 8 slop)
-  0.07% of the heap (87.56% cumulative)
-  0.08% of once-reported (100.00% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total:                12,088 bytes (100.00%) in      30 blocks (100.00%)
-  Unreported:            1,232 bytes ( 10.19%) in      13 blocks ( 43.33%)
-  Once-reported:        10,584 bytes ( 87.56%) in      13 blocks ( 43.33%)
-  Twice-reported:          272 bytes (  2.25%) in       4 blocks ( 13.33%)
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
-  $DMD = '--mode=test'
-  Function = AnalyzeHeap
-  Sample-below size = 1
-}
-
-#-----------------------------------------------------------------
-
-Live {
-  1 block in heap block record 1 of 12
-  8,192 bytes (4,097 requested / 4,095 slop)
-  67.77% of the heap (67.77% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 2 of 12
-  1,024 bytes (1,023 requested / 1 slop)
-  8.47% of the heap (76.24% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  9 blocks in heap block record 3 of 12
-  1,008 bytes (900 requested / 108 slop)
-  8.34% of the heap (84.58% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  6 blocks in heap block record 4 of 12
-  528 bytes (528 requested / 0 slop)
-  4.37% of the heap (88.95% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  6 blocks in heap block record 5 of 12
-  528 bytes (528 requested / 0 slop)
-  4.37% of the heap (93.32% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 6 of 12
-  512 bytes (512 requested / 0 slop)
-  4.24% of the heap (97.55% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 7 of 12
-  80 bytes (79 requested / 1 slop)
-  0.66% of the heap (98.21% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 8 of 12
-  80 bytes (78 requested / 2 slop)
-  0.66% of the heap (98.87% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 9 of 12
-  80 bytes (77 requested / 3 slop)
-  0.66% of the heap (99.54% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 10 of 12
-  32 bytes (30 requested / 2 slop)
-  0.26% of the heap (99.80% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 11 of 12
-  16 bytes (10 requested / 6 slop)
-  0.13% of the heap (99.93% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 12 of 12
-  8 bytes (0 requested / 8 slop)
-  0.07% of the heap (100.00% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total: 12,088 bytes in 30 blocks
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
-  $DMD = '--mode=test'
-  Function = AnalyzeReports
-  Sample-below size = 1
-}
-
-#-----------------------------------------------------------------
-
-Twice-reported {
-  1 block in heap block record 1 of 2
-  80 bytes (77 requested / 3 slop)
-  2.82% of the heap (2.82% cumulative)
-  90.91% of twice-reported (90.91% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-  Reported again at {
-    ... DMD.cpp
-  }
-}
-
-Twice-reported {
-  1 block in heap block record 2 of 2
-  8 bytes (0 requested / 8 slop)
-  0.28% of the heap (3.10% cumulative)
-  9.09% of twice-reported (100.00% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-  Reported again at {
-    ... DMD.cpp
-  }
-}
-
-#-----------------------------------------------------------------
-
-Unreported {
-  9 blocks in heap block record 1 of 3
-  1,008 bytes (900 requested / 108 slop)
-  35.49% of the heap (35.49% cumulative)
-  48.84% of unreported (48.84% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Unreported {
-  6 blocks in heap block record 2 of 3
-  528 bytes (528 requested / 0 slop)
-  18.59% of the heap (54.08% cumulative)
-  25.58% of unreported (74.42% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Unreported {
-  6 blocks in heap block record 3 of 3
-  528 bytes (528 requested / 0 slop)
-  18.59% of the heap (72.68% cumulative)
-  25.58% of unreported (100.00% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-#-----------------------------------------------------------------
-
-Once-reported {
-  1 block in heap block record 1 of 4
-  512 bytes (512 requested / 0 slop)
-  18.03% of the heap (18.03% cumulative)
-  74.42% of once-reported (74.42% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-Once-reported {
-  1 block in heap block record 2 of 4
-  80 bytes (79 requested / 1 slop)
-  2.82% of the heap (20.85% cumulative)
-  11.63% of once-reported (86.05% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-Once-reported {
-  1 block in heap block record 3 of 4
-  80 bytes (78 requested / 2 slop)
-  2.82% of the heap (23.66% cumulative)
-  11.63% of once-reported (97.67% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-Once-reported {
-  1 block in heap block record 4 of 4
-  16 bytes (10 requested / 6 slop)
-  0.56% of the heap (24.23% cumulative)
-  2.33% of once-reported (100.00% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-  Reported at {
-    ... DMD.cpp
-  }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total:                 2,840 bytes (100.00%) in      27 blocks (100.00%)
-  Unreported:            2,064 bytes ( 72.68%) in      21 blocks ( 77.78%)
-  Once-reported:           688 bytes ( 24.23%) in       4 blocks ( 14.81%)
-  Twice-reported:           88 bytes (  3.10%) in       2 blocks (  7.41%)
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
-  $DMD = '--mode=test'
-  Function = AnalyzeHeap
-  Sample-below size = 1
-}
-
-#-----------------------------------------------------------------
-
-Live {
-  9 blocks in heap block record 1 of 9
-  1,008 bytes (900 requested / 108 slop)
-  35.49% of the heap (35.49% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  6 blocks in heap block record 2 of 9
-  528 bytes (528 requested / 0 slop)
-  18.59% of the heap (54.08% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  6 blocks in heap block record 3 of 9
-  528 bytes (528 requested / 0 slop)
-  18.59% of the heap (72.68% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 4 of 9
-  512 bytes (512 requested / 0 slop)
-  18.03% of the heap (90.70% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 5 of 9
-  80 bytes (79 requested / 1 slop)
-  2.82% of the heap (93.52% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 6 of 9
-  80 bytes (78 requested / 2 slop)
-  2.82% of the heap (96.34% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 7 of 9
-  80 bytes (77 requested / 3 slop)
-  2.82% of the heap (99.15% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 8 of 9
-  16 bytes (10 requested / 6 slop)
-  0.56% of the heap (99.72% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 9 of 9
-  8 bytes (0 requested / 8 slop)
-  0.28% of the heap (100.00% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total: 2,840 bytes in 27 blocks
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
-  $DMD = '--mode=test'
-  Function = AnalyzeReports
-  Sample-below size = 128
-}
-
-#-----------------------------------------------------------------
-
-# no twice-reported heap blocks
-
-#-----------------------------------------------------------------
-
-Unreported {
-  ~4 blocks in heap block record 1 of 7
-  ~512 bytes (~512 requested / ~0 slop)
-  35.96% of the heap (35.96% cumulative)
-  35.96% of unreported (35.96% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Unreported {
-  1 block in heap block record 2 of 7
-  256 bytes (256 requested / 0 slop)
-  17.98% of the heap (53.93% cumulative)
-  17.98% of unreported (53.93% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Unreported {
-  1 block in heap block record 3 of 7
-  144 bytes (144 requested / 0 slop)
-  10.11% of the heap (64.04% cumulative)
-  10.11% of unreported (64.04% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Unreported {
-  1 block in heap block record 4 of 7
-  128 bytes (128 requested / 0 slop)
-  8.99% of the heap (73.03% cumulative)
-  8.99% of unreported (73.03% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Unreported {
-  ~1 block in heap block record 5 of 7
-  ~128 bytes (~128 requested / ~0 slop)
-  8.99% of the heap (82.02% cumulative)
-  8.99% of unreported (82.02% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Unreported {
-  ~1 block in heap block record 6 of 7
-  ~128 bytes (~128 requested / ~0 slop)
-  8.99% of the heap (91.01% cumulative)
-  8.99% of unreported (91.01% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Unreported {
-  ~1 block in heap block record 7 of 7
-  ~128 bytes (~128 requested / ~0 slop)
-  8.99% of the heap (100.00% cumulative)
-  8.99% of unreported (100.00% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-#-----------------------------------------------------------------
-
-# no once-reported heap blocks
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total:                ~1,424 bytes (100.00%) in     ~10 blocks (100.00%)
-  Unreported:           ~1,424 bytes (100.00%) in     ~10 blocks (100.00%)
-  Once-reported:            ~0 bytes (  0.00%) in      ~0 blocks (  0.00%)
-  Twice-reported:           ~0 bytes (  0.00%) in      ~0 blocks (  0.00%)
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
-  $DMD = '--mode=test'
-  Function = AnalyzeHeap
-  Sample-below size = 128
-}
-
-#-----------------------------------------------------------------
-
-Live {
-  ~4 blocks in heap block record 1 of 7
-  ~512 bytes (~512 requested / ~0 slop)
-  35.96% of the heap (35.96% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 2 of 7
-  256 bytes (256 requested / 0 slop)
-  17.98% of the heap (53.93% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 3 of 7
-  144 bytes (144 requested / 0 slop)
-  10.11% of the heap (64.04% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  1 block in heap block record 4 of 7
-  128 bytes (128 requested / 0 slop)
-  8.99% of the heap (73.03% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  ~1 block in heap block record 5 of 7
-  ~128 bytes (~128 requested / ~0 slop)
-  8.99% of the heap (82.02% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  ~1 block in heap block record 6 of 7
-  ~128 bytes (~128 requested / ~0 slop)
-  8.99% of the heap (91.01% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-Live {
-  ~1 block in heap block record 7 of 7
-  ~128 bytes (~128 requested / ~0 slop)
-  8.99% of the heap (100.00% cumulative)
-  Allocated at {
-    ... DMD.cpp
-  }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
-  Total: ~1,424 bytes in ~10 blocks
-}
-
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-heap-expected1.txt
@@ -0,0 +1,17 @@
+#-----------------------------------------------------------------
+
+Invocation {
+  $DMD = '--mode=test'
+  Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+# no live heap blocks
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total: 0 bytes in 0 blocks
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-heap-expected2.txt
@@ -0,0 +1,123 @@
+#-----------------------------------------------------------------
+
+Invocation {
+  $DMD = '--mode=test'
+  Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+Live {
+  1 block in heap block record 1 of 12
+  8,192 bytes (4,097 requested / 4,095 slop)
+  67.77% of the heap (67.77% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 2 of 12
+  1,024 bytes (1,023 requested / 1 slop)
+  8.47% of the heap (76.24% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  9 blocks in heap block record 3 of 12
+  1,008 bytes (900 requested / 108 slop)
+  8.34% of the heap (84.58% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  6 blocks in heap block record 4 of 12
+  528 bytes (528 requested / 0 slop)
+  4.37% of the heap (88.95% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  6 blocks in heap block record 5 of 12
+  528 bytes (528 requested / 0 slop)
+  4.37% of the heap (93.32% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 6 of 12
+  512 bytes (512 requested / 0 slop)
+  4.24% of the heap (97.55% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 7 of 12
+  80 bytes (79 requested / 1 slop)
+  0.66% of the heap (98.21% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 8 of 12
+  80 bytes (78 requested / 2 slop)
+  0.66% of the heap (98.87% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 9 of 12
+  80 bytes (77 requested / 3 slop)
+  0.66% of the heap (99.54% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 10 of 12
+  32 bytes (30 requested / 2 slop)
+  0.26% of the heap (99.80% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 11 of 12
+  16 bytes (10 requested / 6 slop)
+  0.13% of the heap (99.93% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 12 of 12
+  8 bytes (0 requested / 8 slop)
+  0.07% of the heap (100.00% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total: 12,088 bytes in 30 blocks
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-heap-expected3.txt
@@ -0,0 +1,96 @@
+#-----------------------------------------------------------------
+
+Invocation {
+  $DMD = '--mode=test'
+  Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+Live {
+  9 blocks in heap block record 1 of 9
+  1,008 bytes (900 requested / 108 slop)
+  35.49% of the heap (35.49% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  6 blocks in heap block record 2 of 9
+  528 bytes (528 requested / 0 slop)
+  18.59% of the heap (54.08% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  6 blocks in heap block record 3 of 9
+  528 bytes (528 requested / 0 slop)
+  18.59% of the heap (72.68% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 4 of 9
+  512 bytes (512 requested / 0 slop)
+  18.03% of the heap (90.70% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 5 of 9
+  80 bytes (79 requested / 1 slop)
+  2.82% of the heap (93.52% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 6 of 9
+  80 bytes (78 requested / 2 slop)
+  2.82% of the heap (96.34% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 7 of 9
+  80 bytes (77 requested / 3 slop)
+  2.82% of the heap (99.15% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 8 of 9
+  16 bytes (10 requested / 6 slop)
+  0.56% of the heap (99.72% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 9 of 9
+  8 bytes (0 requested / 8 slop)
+  0.28% of the heap (100.00% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total: 2,840 bytes in 27 blocks
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-heap-expected4.txt
@@ -0,0 +1,78 @@
+#-----------------------------------------------------------------
+
+Invocation {
+  $DMD = '--mode=test'
+  Sample-below size = 128
+}
+
+#-----------------------------------------------------------------
+
+Live {
+  ~4 blocks in heap block record 1 of 7
+  ~512 bytes (~512 requested / ~0 slop)
+  35.96% of the heap (35.96% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 2 of 7
+  256 bytes (256 requested / 0 slop)
+  17.98% of the heap (53.93% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 3 of 7
+  144 bytes (144 requested / 0 slop)
+  10.11% of the heap (64.04% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  1 block in heap block record 4 of 7
+  128 bytes (128 requested / 0 slop)
+  8.99% of the heap (73.03% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  ~1 block in heap block record 5 of 7
+  ~128 bytes (~128 requested / ~0 slop)
+  8.99% of the heap (82.02% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  ~1 block in heap block record 6 of 7
+  ~128 bytes (~128 requested / ~0 slop)
+  8.99% of the heap (91.01% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Live {
+  ~1 block in heap block record 7 of 7
+  ~128 bytes (~128 requested / ~0 slop)
+  8.99% of the heap (100.00% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total: ~1,424 bytes in ~10 blocks
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-reports-expected1.txt
@@ -0,0 +1,28 @@
+#-----------------------------------------------------------------
+
+Invocation {
+  $DMD = '--mode=test'
+  Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+# no twice-reported heap blocks
+
+#-----------------------------------------------------------------
+
+# no unreported heap blocks
+
+#-----------------------------------------------------------------
+
+# no once-reported heap blocks
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total:                     0 bytes (100.00%) in       0 blocks (100.00%)
+  Unreported:                0 bytes (  0.00%) in       0 blocks (  0.00%)
+  Once-reported:             0 bytes (  0.00%) in       0 blocks (  0.00%)
+  Twice-reported:            0 bytes (  0.00%) in       0 blocks (  0.00%)
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-reports-expected2.txt
@@ -0,0 +1,259 @@
+#-----------------------------------------------------------------
+
+Invocation {
+  $DMD = '--mode=test'
+  Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+Twice-reported {
+  1 block in heap block record 1 of 4
+  80 bytes (79 requested / 1 slop)
+  0.66% of the heap (0.66% cumulative)
+  29.41% of twice-reported (29.41% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+  Reported again at {
+    ... DMD.cpp
+  }
+}
+
+Twice-reported {
+  1 block in heap block record 2 of 4
+  80 bytes (78 requested / 2 slop)
+  0.66% of the heap (1.32% cumulative)
+  29.41% of twice-reported (58.82% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+  Reported again at {
+    ... DMD.cpp
+  }
+}
+
+Twice-reported {
+  1 block in heap block record 3 of 4
+  80 bytes (77 requested / 3 slop)
+  0.66% of the heap (1.99% cumulative)
+  29.41% of twice-reported (88.24% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+  Reported again at {
+    ... DMD.cpp
+  }
+}
+
+Twice-reported {
+  1 block in heap block record 4 of 4
+  32 bytes (30 requested / 2 slop)
+  0.26% of the heap (2.25% cumulative)
+  11.76% of twice-reported (100.00% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+  Reported again at {
+    ... DMD.cpp
+  }
+}
+
+#-----------------------------------------------------------------
+
+Unreported {
+  9 blocks in heap block record 1 of 3
+  1,008 bytes (900 requested / 108 slop)
+  8.34% of the heap (8.34% cumulative)
+  81.82% of unreported (81.82% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Unreported {
+  2 blocks in heap block record 2 of 3
+  112 bytes (112 requested / 0 slop)
+  0.93% of the heap (9.27% cumulative)
+  9.09% of unreported (90.91% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Unreported {
+  2 blocks in heap block record 3 of 3
+  112 bytes (112 requested / 0 slop)
+  0.93% of the heap (10.19% cumulative)
+  9.09% of unreported (100.00% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+#-----------------------------------------------------------------
+
+Once-reported {
+  1 block in heap block record 1 of 11
+  8,192 bytes (4,097 requested / 4,095 slop)
+  67.77% of the heap (67.77% cumulative)
+  77.40% of once-reported (77.40% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+Once-reported {
+  1 block in heap block record 2 of 11
+  1,024 bytes (1,023 requested / 1 slop)
+  8.47% of the heap (76.24% cumulative)
+  9.67% of once-reported (87.07% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+Once-reported {
+  1 block in heap block record 3 of 11
+  512 bytes (512 requested / 0 slop)
+  4.24% of the heap (80.48% cumulative)
+  4.84% of once-reported (91.91% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+Once-reported {
+  2 blocks in heap block record 4 of 11
+  240 bytes (240 requested / 0 slop)
+  1.99% of the heap (82.46% cumulative)
+  2.27% of once-reported (94.18% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+Once-reported {
+  2 blocks in heap block record 5 of 11
+  240 bytes (240 requested / 0 slop)
+  1.99% of the heap (84.45% cumulative)
+  2.27% of once-reported (96.45% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+Once-reported {
+  1 block in heap block record 6 of 11
+  96 bytes (96 requested / 0 slop)
+  0.79% of the heap (85.24% cumulative)
+  0.91% of once-reported (97.35% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+Once-reported {
+  1 block in heap block record 7 of 11
+  96 bytes (96 requested / 0 slop)
+  0.79% of the heap (86.04% cumulative)
+  0.91% of once-reported (98.26% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+Once-reported {
+  1 block in heap block record 8 of 11
+  80 bytes (80 requested / 0 slop)
+  0.66% of the heap (86.70% cumulative)
+  0.76% of once-reported (99.02% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+Once-reported {
+  1 block in heap block record 9 of 11
+  80 bytes (80 requested / 0 slop)
+  0.66% of the heap (87.36% cumulative)
+  0.76% of once-reported (99.77% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+Once-reported {
+  1 block in heap block record 10 of 11
+  16 bytes (10 requested / 6 slop)
+  0.13% of the heap (87.49% cumulative)
+  0.15% of once-reported (99.92% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+Once-reported {
+  1 block in heap block record 11 of 11
+  8 bytes (0 requested / 8 slop)
+  0.07% of the heap (87.56% cumulative)
+  0.08% of once-reported (100.00% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total:                12,088 bytes (100.00%) in      30 blocks (100.00%)
+  Unreported:            1,232 bytes ( 10.19%) in      13 blocks ( 43.33%)
+  Once-reported:        10,584 bytes ( 87.56%) in      13 blocks ( 43.33%)
+  Twice-reported:          272 bytes (  2.25%) in       4 blocks ( 13.33%)
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-reports-expected3.txt
@@ -0,0 +1,136 @@
+#-----------------------------------------------------------------
+
+Invocation {
+  $DMD = '--mode=test'
+  Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+Twice-reported {
+  1 block in heap block record 1 of 2
+  80 bytes (77 requested / 3 slop)
+  2.82% of the heap (2.82% cumulative)
+  90.91% of twice-reported (90.91% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+  Reported again at {
+    ... DMD.cpp
+  }
+}
+
+Twice-reported {
+  1 block in heap block record 2 of 2
+  8 bytes (0 requested / 8 slop)
+  0.28% of the heap (3.10% cumulative)
+  9.09% of twice-reported (100.00% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+  Reported again at {
+    ... DMD.cpp
+  }
+}
+
+#-----------------------------------------------------------------
+
+Unreported {
+  9 blocks in heap block record 1 of 3
+  1,008 bytes (900 requested / 108 slop)
+  35.49% of the heap (35.49% cumulative)
+  48.84% of unreported (48.84% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Unreported {
+  6 blocks in heap block record 2 of 3
+  528 bytes (528 requested / 0 slop)
+  18.59% of the heap (54.08% cumulative)
+  25.58% of unreported (74.42% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Unreported {
+  6 blocks in heap block record 3 of 3
+  528 bytes (528 requested / 0 slop)
+  18.59% of the heap (72.68% cumulative)
+  25.58% of unreported (100.00% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+#-----------------------------------------------------------------
+
+Once-reported {
+  1 block in heap block record 1 of 4
+  512 bytes (512 requested / 0 slop)
+  18.03% of the heap (18.03% cumulative)
+  74.42% of once-reported (74.42% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+Once-reported {
+  1 block in heap block record 2 of 4
+  80 bytes (79 requested / 1 slop)
+  2.82% of the heap (20.85% cumulative)
+  11.63% of once-reported (86.05% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+Once-reported {
+  1 block in heap block record 3 of 4
+  80 bytes (78 requested / 2 slop)
+  2.82% of the heap (23.66% cumulative)
+  11.63% of once-reported (97.67% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+Once-reported {
+  1 block in heap block record 4 of 4
+  16 bytes (10 requested / 6 slop)
+  0.56% of the heap (24.23% cumulative)
+  2.33% of once-reported (100.00% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+  Reported at {
+    ... DMD.cpp
+  }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total:                 2,840 bytes (100.00%) in      27 blocks (100.00%)
+  Unreported:            2,064 bytes ( 72.68%) in      21 blocks ( 77.78%)
+  Once-reported:           688 bytes ( 24.23%) in       4 blocks ( 14.81%)
+  Twice-reported:           88 bytes (  3.10%) in       2 blocks (  7.41%)
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-reports-expected4.txt
@@ -0,0 +1,96 @@
+#-----------------------------------------------------------------
+
+Invocation {
+  $DMD = '--mode=test'
+  Sample-below size = 128
+}
+
+#-----------------------------------------------------------------
+
+# no twice-reported heap blocks
+
+#-----------------------------------------------------------------
+
+Unreported {
+  ~4 blocks in heap block record 1 of 7
+  ~512 bytes (~512 requested / ~0 slop)
+  35.96% of the heap (35.96% cumulative)
+  35.96% of unreported (35.96% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Unreported {
+  1 block in heap block record 2 of 7
+  256 bytes (256 requested / 0 slop)
+  17.98% of the heap (53.93% cumulative)
+  17.98% of unreported (53.93% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Unreported {
+  1 block in heap block record 3 of 7
+  144 bytes (144 requested / 0 slop)
+  10.11% of the heap (64.04% cumulative)
+  10.11% of unreported (64.04% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Unreported {
+  1 block in heap block record 4 of 7
+  128 bytes (128 requested / 0 slop)
+  8.99% of the heap (73.03% cumulative)
+  8.99% of unreported (73.03% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Unreported {
+  ~1 block in heap block record 5 of 7
+  ~128 bytes (~128 requested / ~0 slop)
+  8.99% of the heap (82.02% cumulative)
+  8.99% of unreported (82.02% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Unreported {
+  ~1 block in heap block record 6 of 7
+  ~128 bytes (~128 requested / ~0 slop)
+  8.99% of the heap (91.01% cumulative)
+  8.99% of unreported (91.01% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+Unreported {
+  ~1 block in heap block record 7 of 7
+  ~128 bytes (~128 requested / ~0 slop)
+  8.99% of the heap (100.00% cumulative)
+  8.99% of unreported (100.00% cumulative)
+  Allocated at {
+    ... DMD.cpp
+  }
+}
+
+#-----------------------------------------------------------------
+
+# no once-reported heap blocks
+
+#-----------------------------------------------------------------
+
+Summary {
+  Total:                ~1,424 bytes (100.00%) in     ~10 blocks (100.00%)
+  Unreported:           ~1,424 bytes (100.00%) in     ~10 blocks (100.00%)
+  Once-reported:            ~0 bytes (  0.00%) in      ~0 blocks (  0.00%)
+  Twice-reported:           ~0 bytes (  0.00%) in      ~0 blocks (  0.00%)
+}
+
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -929,42 +929,44 @@ class RunDmd(MachCommandBase):
         help=('Command-line arguments to be passed through to the program. '
               'Not specifying a -profile or -P option will result in a '
               'temporary profile being used. If passing -params use a "--" to '
               'indicate the start of params to pass to firefox.'))
     @CommandArgument('--remote', '-r', action='store_true',
         help='Do not pass the -no-remote argument by default.')
     @CommandArgument('--background', '-b', action='store_true',
         help='Do not pass the -foreground argument by default on Mac')
+    @CommandArgument('--noprofile', '-n', action='store_true',
+        help='Do not pass the -profile argument by default.')
     @CommandArgument('--sample-below', default=None, type=str,
         help='The sample size to use, [1..n]. Default is 4093.')
     @CommandArgument('--max-frames', default=None, type=str,
         help='The max number of stack frames to capture in allocation traces, [1..24] Default is 24.')
-    @CommandArgument('--max-records', default=None, type=str,
-        help='Number of stack trace records to print of each kind, [1..1000000]. Default is 1000.')
-    def dmd(self, params, remote, background, sample_below, max_frames, max_records):
-        args = get_run_args(self, params, remote, background)
+    @CommandArgument('--show-dump-stats', action='store_true',
+        help='Show stats when doing dumps.')
+    def dmd(self, params, remote, background, noprofile, sample_below, max_frames, show_dump_stats):
+        args = get_run_args(self, params, remote, background, noprofile)
         if not args:
             return 1
 
         lib_dir = os.path.join(self.distdir, 'lib')
         lib_name = self.substs['DLL_PREFIX'] + 'dmd' + self.substs['DLL_SUFFIX']
         dmd_lib = os.path.join(lib_dir, lib_name)
         if not os.path.exists(dmd_lib):
             print("You need to build with |--enable-dmd| to use dmd.")
             return 1
 
         dmd_params = []
 
         if sample_below:
             dmd_params.append('--sample-below=' + sample_below)
         if max_frames:
             dmd_params.append('--max-frames=' + max_frames)
-        if max_records:
-            dmd_params.append('--max-records=' + max_records)
+        if show_dump_stats:
+            dmd_params.append('--show-dump-stats=yes')
 
         if dmd_params:
             dmd_str = " ".join(dmd_params)
         else:
             dmd_str = "1"
 
         env_vars = {
             "Darwin": {
--- a/security/manager/ssl/src/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/src/SSLServerCertVerification.cpp
@@ -398,18 +398,17 @@ CertErrorRunnable::CheckCertOverrides()
     NS_ERROR("CertErrorRunnable::CheckCertOverrides called off main thread");
     return new SSLServerCertVerificationResult(mInfoObject,
                                                mDefaultErrorCodeToReport);
   }
 
   int32_t port;
   mInfoObject->GetPort(&port);
 
-  nsCString hostWithPortString;
-  hostWithPortString.AppendASCII(mInfoObject->GetHostNameRaw());
+  nsAutoCString hostWithPortString(mInfoObject->GetHostName());
   hostWithPortString.Append(':');
   hostWithPortString.AppendInt(port);
 
   uint32_t remaining_display_errors = mCollectedErrors;
 
   nsresult nsrv;
 
   // Enforce Strict-Transport-Security for hosts that are "STS" hosts:
@@ -435,17 +434,17 @@ CertErrorRunnable::CheckCertOverrides()
     // it is fine to continue without the nsICertOverrideService
 
     uint32_t overrideBits = 0;
 
     if (overrideService)
     {
       bool haveOverride;
       bool isTemporaryOverride; // we don't care
-      nsCString hostString(mInfoObject->GetHostName());
+      const nsACString& hostString(mInfoObject->GetHostName());
       nsrv = overrideService->HasMatchingOverride(hostString, port,
                                                   mCert,
                                                   &overrideBits,
                                                   &isTemporaryOverride,
                                                   &haveOverride);
       if (NS_SUCCEEDED(nsrv) && haveOverride)
       {
        // remove the errors that are already overriden
@@ -762,35 +761,20 @@ AccumulateSubjectCommonNameTelemetry(con
   }
 }
 
 // Returns true if and only if commonName ends with altName (minus its leading
 // "*"). altName has already been checked to be of the form "*.<something>".
 // commonName may be NULL.
 static bool
 TryMatchingWildcardSubjectAltName(const char* commonName,
-                                  nsDependentCString altName)
+                                  const nsACString& altName)
 {
-  if (!commonName) {
-    return false;
-  }
-  // altNameSubstr is now ".<something>"
-  nsDependentCString altNameSubstr(altName.get() + 1, altName.Length() - 1);
-  nsDependentCString commonNameStr(commonName, strlen(commonName));
-  int32_t altNameIndex = commonNameStr.Find(altNameSubstr);
-  // This only matches if the end of commonNameStr is the altName without
-  // the '*'.
-  // Consider this.example.com and *.example.com:
-  // "this.example.com".Find(".example.com") is 4
-  // 4 + ".example.com".Length() == 4 + 12 == 16 == "this.example.com".Length()
-  // Now this.example.com and *.example:
-  // "this.example.com".Find(".example") is 4
-  // 4 + ".example".Length() == 4 + 8 == 12 != "this.example.com".Length()
-  return altNameIndex >= 0 &&
-         altNameIndex + altNameSubstr.Length() == commonNameStr.Length();
+  return commonName &&
+         StringEndsWith(nsDependentCString(commonName), Substring(altName, 1));
 }
 
 // Gathers telemetry on Baseline Requirements 9.2.1 (Subject Alternative
 // Names Extension) and 9.2.2 (Subject Common Name Field).
 // Specifically:
 //  - whether or not the subject common name field is present
 //  - whether or not the subject alternative names extension is present
 //  - if there is a malformed entry in the subject alt. names extension
@@ -854,23 +838,23 @@ GatherBaselineRequirementsTelemetry(cons
   }
 
   CERTGeneralName* currentName = subjectAltNames;
   bool commonNameInSubjectAltNames = false;
   bool nonDNSNameOrIPAddressPresent = false;
   bool malformedDNSNameOrIPAddressPresent = false;
   bool nonFQDNPresent = false;
   do {
-    nsDependentCString altName;
+    nsAutoCString altName;
     if (currentName->type == certDNSName) {
       altName.Assign(reinterpret_cast<char*>(currentName->name.other.data),
                      currentName->name.other.len);
-      nsDependentCString altNameWithoutWildcard(altName);
-      if (altNameWithoutWildcard.Find("*.") == 0) {
-        altNameWithoutWildcard.Assign(altName.get() + 2, altName.Length() - 2);
+      nsDependentCString altNameWithoutWildcard(altName, 0);
+      if (StringBeginsWith(altNameWithoutWildcard, NS_LITERAL_CSTRING("*."))) {
+        altNameWithoutWildcard.Rebind(altName, 2);
         commonNameInSubjectAltNames |=
           TryMatchingWildcardSubjectAltName(commonName.get(), altName);
       }
       // net_IsValidHostName appears to return true for valid IP addresses,
       // which would be invalid for a DNS name.
       // Note that the net_IsValidHostName check will catch things like
       // "a.*.example.com".
       if (!net_IsValidHostName(altNameWithoutWildcard) ||
@@ -893,29 +877,29 @@ GatherBaselineRequirementsTelemetry(cons
         memcpy(&addr.inet.ip, currentName->name.other.data,
                currentName->name.other.len);
         if (PR_NetAddrToString(&addr, buf, sizeof(buf) - 1) != PR_SUCCESS) {
         PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
                ("BR telemetry: IPAddress (v4) not valid (for '%s')\n",
                 commonName.get()));
           malformedDNSNameOrIPAddressPresent = true;
         } else {
-          altName.Assign(buf, strlen(buf));
+          altName.Assign(buf);
         }
       } else if (currentName->name.other.len == 16) {
         addr.inet.family = PR_AF_INET6;
         memcpy(&addr.ipv6.ip, currentName->name.other.data,
                currentName->name.other.len);
         if (PR_NetAddrToString(&addr, buf, sizeof(buf) - 1) != PR_SUCCESS) {
         PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
                ("BR telemetry: IPAddress (v6) not valid (for '%s')\n",
                 commonName.get()));
           malformedDNSNameOrIPAddressPresent = true;
         } else {
-          altName.Assign(buf, strlen(buf));
+          altName.Assign(buf);
         }
       } else {
         PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
                ("BR telemetry: IPAddress not valid (for '%s')\n",
                 commonName.get()));
         malformedDNSNameOrIPAddressPresent = true;
       }
     } else {
--- a/security/sandbox/win/src/warnonlysandbox/wosCallbacks.h
+++ b/security/sandbox/win/src/warnonlysandbox/wosCallbacks.h
@@ -54,24 +54,26 @@ SetProvideLogFunctionCb(ProvideLogFuncti
   sProvideLogFunctionCb = aProvideLogFunctionCb;
 }
 
 #ifdef MOZ_STACKWALKING
 static uint32_t sStackTraceDepth = 0;
 
 // NS_WalkStackCallback to write a formatted stack frame to an ostringstream.
 static void
-StackFrameToOStringStream(void* aPC, void* aSP, void* aClosure)
+StackFrameToOStringStream(uint32_t aFrameNumber, void* aPC, void* aSP,
+                          void* aClosure)
 {
   std::ostringstream* stream = static_cast<std::ostringstream*>(aClosure);
   nsCodeAddressDetails details;
   char buf[1024];
   NS_DescribeCodeAddress(aPC, &details);
-  NS_FormatCodeAddressDetails(aPC, &details, buf, sizeof(buf));
-  *stream << "--" << buf;
+  NS_FormatCodeAddressDetails(buf, sizeof(buf), aFrameNumber, aPC, &details);
+  *stream << "--" << buf << '\n';
+  stream->flush();
 }
 #endif
 
 // Log to the browser console and, if DEBUG build, stderr.
 static void
 Log(const char* aMessageType,
     const char* aFunctionName,
     const char* aContext,
--- a/toolkit/content/tests/chrome/findbar_events_window.xul
+++ b/toolkit/content/tests/chrome/findbar_events_window.xul
@@ -16,17 +16,17 @@
   <script type="application/javascript"><![CDATA[
     const Ci = Components.interfaces;
     const Cc = Components.classes;
     const Cr = Components.results;
 
     var gFindBar = null;
     var gBrowser;
 
-    var imports = ["SimpleTest", "ok"];
+    var imports = ["SimpleTest", "ok", "is"];
     for each (var name in imports) {
       window[name] = window.opener.wrappedJSObject[name];
     }
 
     function finish() {
       window.close();
       SimpleTest.finish();
     }
@@ -66,31 +66,38 @@
       gFindBar.open();
       gFindBar.onFindCommand();
       nextTest();
     }
 
     function checkSelection(done) {
       SimpleTest.executeSoon(function() {
         var selected = gBrowser.contentWindow.getSelection();
-        ok(selected == "", "No text is selected");
+        is(selected, "", "No text is selected");
 
         var controller = gFindBar.browser.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsISelectionDisplay)
                                  .QueryInterface(Ci.nsISelectionController);
         var selection = controller.getSelection(controller.SELECTION_FIND);
-        ok(selection.rangeCount == 0, "No text is highlighted");
+        is(selection.rangeCount, 0, "No text is highlighted");
         done();
       });
     }
 
+    function once(node, eventName, callback) {
+      node.addEventListener(eventName, function clb(e) {
+        node.removeEventListener(eventName, clb);
+        callback(e);
+      })
+    }
+
     function testFind(done) {
-      var findTriggered = false;
+      var eventTriggered = false;
       var query = "t";
-      gFindBar.addEventListener("find", function(e) {
+      once(gFindBar, "find", function(e) {
         eventTriggered = true;
         ok(e.detail.query === query, "find event query should match '" + query + "'");
         e.preventDefault();
         // Since we're preventing the default make sure nothing was selected.
         checkSelection(done);
       });
 
       // Put some text in the find box.
@@ -98,44 +105,48 @@
       event.initKeyEvent("keypress", true, true, null, false, false,
                          false, false, 0, query.charCodeAt(0));
       gFindBar._findField.inputField.dispatchEvent(event);
       ok(eventTriggered, "find event should be triggered");
     }
 
     function testFindAgain(done) {
       var eventTriggered = false;
-      gFindBar.addEventListener("findagain", function(e) {
+      once(gFindBar, "findagain", function(e) {
         eventTriggered = true;
         e.preventDefault();
         // Since we're preventing the default make sure nothing was selected.
         checkSelection(done);
       });
 
       gFindBar.onFindAgainCommand();
       ok(eventTriggered, "findagain event should be triggered");
     }
 
     function testCaseSensitivity() {
       var eventTriggered = false;
-      gFindBar.addEventListener("findcasesensitivitychange", function(e) {
+      once(gFindBar, "findcasesensitivitychange", function(e) {
         eventTriggered = true;
         ok(e.detail.caseSensitive, "find should be case sensitive");
       });
 
       var matchCaseCheckbox = gFindBar.getElement("find-case-sensitive");
       matchCaseCheckbox.click();
       ok(eventTriggered, "findcasesensitivitychange should be triggered");
+
+      // Changing case sensitivity does the search so clear the selected text
+      // before the next test.
+      gBrowser.contentWindow.getSelection().removeAllRanges();
     }
 
     function testHighlight(done) {
       // Update the find state so the highlight button is clickable.
       gFindBar.updateControlState(Ci.nsITypeAheadFind.FIND_FOUND, false);
       var eventTriggered = false;
-      gFindBar.addEventListener("findhighlightallchange", function(e) {
+      once(gFindBar, "findhighlightallchange", function(e) {
         eventTriggered = true;
         ok(e.detail.highlightAll, "find event should have highlight all set");
         e.preventDefault();
         // Since we're preventing the default make sure nothing was highlighted.
         SimpleTest.executeSoon(function() {
           checkSelection(done);
         });
       });
--- a/toolkit/content/tests/chrome/findbar_window.xul
+++ b/toolkit/content/tests/chrome/findbar_window.xul
@@ -17,16 +17,19 @@
           src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"/>
 
   <script type="application/javascript"><![CDATA[
     const Ci = Components.interfaces;
     const Cc = Components.classes;
     const Cr = Components.results;
+    const Cu = Components.utils;
+    const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+    var { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 
     const SAMPLE_URL = "http://www.mozilla.org/";
     const SAMPLE_TEXT = "Some text in a text field.";
     const SEARCH_TEXT = "Text Test";
 
     var gFindBar = null;
     var gBrowser;
 
@@ -50,16 +53,19 @@
       },
 
       onBeforeLinkTraversal: function() { }
     };
 
     function ok(condition, message) {
       window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
     }
+    function is(a, b, message) {
+      window.opener.wrappedJSObject.SimpleTest.is(a, b, message);
+    }
     function finish() {
       window.close();
       window.opener.wrappedJSObject.SimpleTest.finish();
     }
 
     function onLoad() {
       window.QueryInterface(Ci.nsIInterfaceRequestor)
             .getInterface(Ci.nsIWebNavigation)
@@ -78,46 +84,46 @@
       setTimeout(_delayedOnLoad, 1000);
     }
 
     function _delayedOnPageShow() {
       // setTimeout to the test runs after painting suppression ends
       setTimeout(onPageShow, 0);
     }
 
-    function onPageShow() {
+    let onPageShow = Task.async(function* () {
       testNormalFind();
       gFindBar.close();
       ok(gFindBar.hidden, "Failed to close findbar after testNormalFind");
       testNormalFindWithComposition();
       gFindBar.close();
       ok(gFindBar.hidden, "findbar should be hidden after testNormalFindWithComposition");
       testAutoCaseSensitivityUI();
       testQuickFindText();
       gFindBar.close();
       ok(gFindBar.hidden, "Failed to close findbar after testQuickFindText");
       testFindWithHighlight();
       gFindBar.close();
       ok(gFindBar.hidden, "Failed to close findbar after testFindWithHighlight");
       testFindbarSelection();
       testDrop();
       testQuickFindLink();
-      if (gHasFindClipboard)
-        testStatusText(afterStatusText);
-      else
-        afterStatusText();
-
-      function afterStatusText() {
-        testFindCountUI(function() {
-          gFindBar.close();
-          ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI");
-          testQuickFindClose();
-        });
+      if (gHasFindClipboard) {
+        yield testStatusText();
       }
-    }
+      yield testFindCountUI();
+      gFindBar.close();
+      ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI");
+      yield testFindAfterCaseChanged();
+      gFindBar.close();
+      yield testFailedStringReset();
+      gFindBar.close();
+      yield testQuickFindClose();
+      finish();
+    });
 
     function testFindbarSelection() {
       function checkFindbarState(aTestName, aExpSelection) {
         document.getElementById("cmd_find").doCommand();
         ok(!gFindBar.hidden, "testFindbarSelection: failed to open findbar: " + aTestName);
         ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField,
            "testFindbarSelection: find field is not focused: " + aTestName);
         if (!gHasFindClipboard) {
@@ -153,36 +159,40 @@
     }
 
     function testDrop()
     {
       gFindBar.open();
       // use an dummy image to start the drag so it doesn't get interrupted by a selection
       var img = gBrowser.contentDocument.getElementById("img");
       synthesizeDrop(img, gFindBar._findField, [[ {type: "text/plain", data: "Rabbits" } ]], "copy", window);
-      window.opener.wrappedJSObject.SimpleTest.is(gFindBar._findField.inputField.value, "Rabbits", "drop on findbar");
+      is(gFindBar._findField.inputField.value, "Rabbits", "drop on findbar");
       gFindBar.close();
     }
 
     function testQuickFindClose() {
+      let deferred = Promise.defer();
       var _isClosedCallback = function() {
         ok(gFindBar.hidden,
            "_isClosedCallback: Failed to auto-close quick find bar after " +
            gFindBar._quickFindTimeoutLength + "ms");
-        finish();
+        deferred.resolve();
       };
       setTimeout(_isClosedCallback, gFindBar._quickFindTimeoutLength + 100);
+      return deferred.promise;
     }
 
-    function testStatusText(aCallback) {
+    function testStatusText() {
+      let deferred = Promise.defer();
       var _delayedCheckStatusText = function() {
         ok(gStatusText == SAMPLE_URL, "testStatusText: Failed to set status text of found link");
-        aCallback();
+        deferred.resolve();
       };
       setTimeout(_delayedCheckStatusText, 100);
+      return deferred.promise;
     }
 
     function enterStringIntoFindField(aString) {
       for (var i=0; i < aString.length; i++) {
         var event = document.createEvent("KeyEvents");
         event.initKeyEvent("keypress", true, true, null, false, false,
                            false, false, 0, aString.charCodeAt(i));
         gFindBar._findField.inputField.dispatchEvent(event);
@@ -387,16 +397,18 @@
 
       enterStringIntoFindField(SEARCH_TEXT);
       ok(gBrowser.contentWindow.getSelection() == SEARCH_TEXT,
          "testQuickFindText: failed to find '" + SEARCH_TEXT + "'");
       testClipboardSearchString(SEARCH_TEXT);
     }
 
     function testFindCountUI(callback) {
+      let deferred = Promise.defer();
+
       clearFocus();
       document.getElementById("cmd_find").doCommand();
 
       ok(!gFindBar.hidden, "testFindCountUI: failed to open findbar");
       ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField,
          "testFindCountUI: find field is not focused");
 
       let matchCase = gFindBar.getElement("find-case-sensitive");
@@ -420,19 +432,19 @@
         text: "texxx",
         current: 0,
         total: 0
       }];
       let regex = /([\d]*)\sof\s([\d]*)/;
       let timeout = gFindBar._matchesCountTimeoutLength + 20;
 
       function assertMatches(aTest, aMatches) {
-        window.opener.wrappedJSObject.SimpleTest.is(aMatches[1], aTest.current,
+        is(aMatches[1], aTest.current,
           "Currently highlighted match should be at " + aTest.current);
-        window.opener.wrappedJSObject.SimpleTest.is(aMatches[2], aTest.total,
+        is(aMatches[2], aTest.total,
           "Total amount of matches should be " + aTest.total);
       }
 
       function* generatorTest() {
         for (let test of tests) {
           gFindBar.clear();
           yield;
           enterStringIntoFindField(test.text);
@@ -449,26 +461,74 @@
               let current = (test.current + i - 1) % test.total + 1;
               assertMatches({
                 current: current,
                 total: test.total
               }, foundMatches.value.match(regex));
             }
           }
         }
-        callback();
+        deferred.resolve();
       }
       let test = generatorTest();
       let resultListener = {
         onMatchesCountResult: function() {
           test.next();
         }
       };
       gFindBar.browser.finder.addResultListener(resultListener);
       test.next();
+      return deferred.promise;
+    }
+
+    // See bug 1051187.
+    function testFindAfterCaseChanged() {
+      let deferred = Promise.defer();
+      document.getElementById("cmd_find").doCommand();
+
+      // Search to set focus on "Text Test" so that searching for "t" selects first
+      // (upper case!) "T".
+      enterStringIntoFindField(SEARCH_TEXT);
+      gFindBar.clear();
+
+      let prefsvc = Cc["@mozilla.org/preferences-service;1"]
+                      .getService(Ci.nsIPrefBranch);
+      prefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 0);
+
+      enterStringIntoFindField("t");
+      is(gBrowser.contentWindow.getSelection(), "T", "First T should be selected.");
+
+      prefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 1);
+      setTimeout(function() {
+        is(gBrowser.contentWindow.getSelection(), "t", "First t should be selected.");
+        deferred.resolve();
+      }, 0);
+      return deferred.promise;
+    }
+
+    // Make sure that _findFailedString is cleared:
+    // 1. Do a search that fails with case sensitivity but matches with no case sensitivity.
+    // 2. Uncheck case sensitivity button to match the string.
+    function testFailedStringReset(aCallback) {
+      let deferred = Promise.defer();
+      document.getElementById("cmd_find").doCommand();
+
+      var prefsvc = Cc["@mozilla.org/preferences-service;1"].
+                    getService(Components.interfaces.nsIPrefBranch);
+      prefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 1);
+
+      enterStringIntoFindField(SEARCH_TEXT.toUpperCase());
+      is(gBrowser.contentWindow.getSelection(), "", "Not found.");
+
+      prefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 0);
+      setTimeout(function() {
+        is(gBrowser.contentWindow.getSelection(), SEARCH_TEXT, "Search text should be selected.");
+        deferred.resolve();
+      }, 0);
+      return deferred.resolve();
     }
 
     function testClipboardSearchString(aExpected) {
       if (!gHasFindClipboard)
         return;
 
       if (!aExpected)
         aExpected = "";
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.xml
@@ -533,16 +533,18 @@
         -   2 - auto = case sensitive iff match string contains upper case letters
         -   @see _shouldBeCaseSensitive
         -->
       <method name="_setCaseSensitivity">
         <parameter name="aCaseSensitivity"/>
         <body><![CDATA[
           this._typeAheadCaseSensitive = aCaseSensitivity;
           this._updateCaseSensitivity();
+          this._findFailedString = null;
+          this._find();
           if (this.getElement("highlight").checked)
             this._setHighlightTimeout();
 
           this._dispatchFindEvent("casesensitivitychange");
         ]]></body>
       </method>
 
       <field name="_strBundle">null</field>
--- a/toolkit/content/widgets/radio.xml
+++ b/toolkit/content/widgets/radio.xml
@@ -448,20 +448,20 @@
           // Just clear out the parent's cached list of radio children
           var control = this.control;
           if (control)
             control._radioChildren = null;
         ]]>
       </constructor>
       <destructor>
         <![CDATA[
-          if (!this.radioGroup)
+          if (!this.control)
             return;
 
-          var radioList = this.radioGroup.mRadioChildren;
+          var radioList = this.control._radioChildren;
           if (!radioList)
             return;
           for (var i = 0; i < radioList.length; ++i) {
             if (radioList[i] == this) {
               radioList.splice(i, 1);
               return;
             }
           }
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -1508,16 +1508,20 @@
                     addListener(this.controlsSpacer, "click", this.clickToPlayClickHandler);
                     addListener(this.controlsSpacer, "dblclick", this.toggleFullscreen);
 
                     addListener(this.videocontrols, "resizevideocontrols", this.adjustControlSize);
                     addListener(this.videocontrols, "transitionend", this.onTransitionEnd);
                     addListener(this.video.ownerDocument, "mozfullscreenchange", this.onFullscreenChange);
                     addListener(this.video, "keypress", this.keyHandler);
 
+                    addListener(this.videocontrols, "dragstart", function(event) {
+                        event.preventDefault(); //prevent dragging of controls image (bug 517114)
+                    });
+
                     this.log("--- videocontrols initialized ---");
                 }
             };
             this.Utils.init(this);
             ]]>
         </constructor>
         <destructor>
             <![CDATA[
--- a/toolkit/xre/nsSigHandlers.cpp
+++ b/toolkit/xre/nsSigHandlers.cpp
@@ -51,24 +51,26 @@ static unsigned int _gdb_sleep_duration 
 #include "nsStackWalk.h"
 
 // NB: keep me up to date with the same variable in
 // ipc/chromium/chrome/common/ipc_channel_posix.cc
 static const int kClientChannelFd = 3;
 
 extern "C" {
 
-static void PrintStackFrame(void *aPC, void *aSP, void *aClosure)
+static void PrintStackFrame(uint32_t aFrameNumber, void *aPC, void *aSP,
+                            void *aClosure)
 {
   char buf[1024];
   nsCodeAddressDetails details;
 
   NS_DescribeCodeAddress(aPC, &details);
-  NS_FormatCodeAddressDetails(aPC, &details, buf, sizeof(buf));
-  fputs(buf, stdout);
+  NS_FormatCodeAddressDetails(buf, sizeof(buf), aFrameNumber, aPC, &details);
+  fprintf(stdout, "%s\n", buf);
+  fflush(stdout);
 }
 
 }
 
 void
 ah_crap_handler(int signum)
 {
   printf("\nProgram %s (pid = %d) received signal %d.\n",
--- a/tools/profiler/ProfilerMarkers.cpp
+++ b/tools/profiler/ProfilerMarkers.cpp
@@ -164,8 +164,23 @@ TouchDataPayload::TouchDataPayload(const
 void
 TouchDataPayload::streamPayloadImpl(JSStreamWriter& b)
 {
   b.BeginObject();
   b.NameValue("x", mPoint.x);
   b.NameValue("y", mPoint.y);
   b.EndObject();
 }
+
+VsyncPayload::VsyncPayload(mozilla::TimeStamp aVsyncTimestamp)
+  : ProfilerMarkerPayload(aVsyncTimestamp, aVsyncTimestamp, nullptr)
+  , mVsyncTimestamp(aVsyncTimestamp)
+{
+}
+
+void
+VsyncPayload::streamPayloadImpl(JSStreamWriter& b)
+{
+  b.BeginObject();
+  b.NameValue("vsync", profiler_time(mVsyncTimestamp));
+  b.NameValue("category", "VsyncTimestamp");
+  b.EndObject();
+}
--- a/tools/profiler/ProfilerMarkers.h
+++ b/tools/profiler/ProfilerMarkers.h
@@ -163,9 +163,27 @@ protected:
   virtual void
   streamPayload(JSStreamWriter& b) { return streamPayloadImpl(b); }
 
 private:
   void streamPayloadImpl(JSStreamWriter& b);
   mozilla::ScreenIntPoint mPoint;
 };
 
+/**
+ * Tracks when a vsync occurs according to the HardwareComposer.
+ */
+class VsyncPayload : public ProfilerMarkerPayload
+{
+public:
+  explicit VsyncPayload(mozilla::TimeStamp aVsyncTimestamp);
+  virtual ~VsyncPayload() {}
+
+protected:
+  virtual void
+  streamPayload(JSStreamWriter& b) { return streamPayloadImpl(b); }
+
+private:
+  void streamPayloadImpl(JSStreamWriter& b);
+  mozilla::TimeStamp mVsyncTimestamp;
+};
+
 #endif // PROFILER_MARKERS_H
--- a/tools/profiler/TableTicker.cpp
+++ b/tools/profiler/TableTicker.cpp
@@ -538,17 +538,18 @@ void mergeStacksIntoProfile(ThreadProfil
     MOZ_ASSERT(nativeIndex >= 0);
     aProfile.addTag(ProfileEntry('l', (void*)aNativeStack.pc_array[nativeIndex]));
     nativeIndex--;
   }
 }
 
 #ifdef USE_NS_STACKWALK
 static
-void StackWalkCallback(void* aPC, void* aSP, void* aClosure)
+void StackWalkCallback(uint32_t aFrameNumber, void* aPC, void* aSP,
+                       void* aClosure)
 {
   NativeStack* nativeStack = static_cast<NativeStack*>(aClosure);
   MOZ_ASSERT(nativeStack->count < nativeStack->size);
   nativeStack->sp_array[nativeStack->count] = aSP;
   nativeStack->pc_array[nativeStack->count] = aPC;
   nativeStack->count++;
 }
 
@@ -562,18 +563,21 @@ void TableTicker::doNativeBacktrace(Thre
   void* sp_array[1000];
   NativeStack nativeStack = {
     pc_array,
     sp_array,
     mozilla::ArrayLength(pc_array),
     0
   };
 
-  // Start with the current function.
-  StackWalkCallback(aSample->pc, aSample->sp, &nativeStack);
+  // Start with the current function. We use 0 as the frame number here because
+  // the FramePointerStackWalk() and NS_StackWalk() calls below will use 1..N.
+  // This is a bit weird but it doesn't matter because StackWalkCallback()
+  // doesn't use the frame number argument.
+  StackWalkCallback(/* frameNumber */ 0, aSample->pc, aSample->sp, &nativeStack);
 
   uint32_t maxFrames = uint32_t(nativeStack.size - nativeStack.count);
 #ifdef XP_MACOSX
   pthread_t pt = GetProfiledThread(aSample->threadProfile->GetPlatformData());
   void *stackEnd = reinterpret_cast<void*>(-1);
   if (pt)
     stackEnd = static_cast<char*>(pthread_get_stackaddr_np(pt));
   nsresult rv = NS_OK;
--- a/tools/rb/fix_linux_stack.py
+++ b/tools/rb/fix_linux_stack.py
@@ -1,26 +1,17 @@
 #!/usr/bin/python
 # vim:sw=4:ts=4:et:
 # 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/.
 
-# This script uses addr2line (part of binutils) to process the output of
-# nsTraceRefcnt's Linux stack walking code.  This is useful for two
-# things:
-#  (1) Getting line number information out of
-#      |nsTraceRefcnt::WalkTheStack|'s output in debug builds.
-#  (2) Getting function names out of |nsTraceRefcnt::WalkTheStack|'s
-#      output on optimized builds (where it mostly prints UNKNOWN
-#      because only a handful of symbols are exported from component
-#      libraries).
-#
-# Use the script by piping output containing stacks (such as raw stacks
-# or make-tree.pl balance trees) through this script.
+# This script uses addr2line (part of binutils) to post-process the entries
+# produced by NS_FormatCodeAddress(), which on Linux often lack a function
+# name, a file name and a line number.
 
 import subprocess
 import sys
 import re
 import os
 import pty
 import termios
 from StringIO import StringIO
@@ -291,35 +282,30 @@ def addressToSymbol(file, address):
     else:
         (converter, address_adjustment, cache) = addr2lines[file]
     if address in cache:
         return cache[address]
     result = converter.convert(hex(int(address, 16) + address_adjustment))
     cache[address] = result
     return result
 
-line_re = re.compile("^(.*) ?\[([^ ]*) \+(0x[0-9A-F]{1,8})\](.*)$")
-balance_tree_re = re.compile("^([ \|0-9-]*)(.*)$")
+# Matches lines produced by NS_FormatCodeAddress().
+line_re = re.compile("^(.*#\d+: )(.+)\[(.+) \+(0x[0-9A-Fa-f]+)\](.*)$")
 
 def fixSymbols(line):
     result = line_re.match(line)
     if result is not None:
-        # before allows preservation of balance trees
-        # after allows preservation of counts
-        (before, file, address, after) = result.groups()
+        (before, fn, file, address, after) = result.groups()
 
         if os.path.exists(file) and os.path.isfile(file):
-            # throw away the bad symbol, but keep balance tree structure
-            (before, badsymbol) = balance_tree_re.match(before).groups()
-
             (name, fileline) = addressToSymbol(file, address)
 
             # If addr2line gave us something useless, keep what we had before.
             if name == "??":
-                name = badsymbol
+                name = fn
             if fileline == "??:0" or fileline == "??:?":
                 fileline = file
 
             nl = '\n' if line[-1] == '\n' else ''
             return "%s%s (%s)%s%s" % (before, name, fileline, after, nl)
         else:
             sys.stderr.write("Warning: File \"" + file + "\" does not exist.\n")
             return line
--- a/tools/rb/fix_macosx_stack.py
+++ b/tools/rb/fix_macosx_stack.py
@@ -1,24 +1,17 @@
 #!/usr/bin/python
 # vim:sw=4:ts=4:et:
 # 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/.
 
-# This script uses atos to process the output of nsTraceRefcnt's Mac OS
-# X stack walking code.  This is useful for two things:
-#  (1) Getting line number information out of
-#      |nsTraceRefcnt::WalkTheStack|'s output in debug builds.
-#  (2) Getting function names out of |nsTraceRefcnt::WalkTheStack|'s
-#      output on all builds (where it mostly prints UNKNOWN because only
-#      a handful of symbols are exported from component libraries).
-#
-# Use the script by piping output containing stacks (such as raw stacks
-# or make-tree.pl balance trees) through this script.
+# This script uses |atos| to post-process the entries produced by
+# NS_FormatCodeAddress(), which on Mac often lack a file name and a line
+# number.
 
 import subprocess
 import sys
 import re
 import os
 import pty
 import termios
 
@@ -94,26 +87,24 @@ def cxxfilt(sym):
         globals()["cxxfilt_proc"] = subprocess.Popen(['c++filt',
                                                       '--no-strip-underscores',
                                                       '--format', 'gnu-v3'],
                                                      stdin=subprocess.PIPE,
                                                      stdout=subprocess.PIPE)
     cxxfilt_proc.stdin.write(sym + "\n")
     return cxxfilt_proc.stdout.readline().rstrip("\n")
 
-line_re = re.compile("^(.*) ?\[([^ ]*) \+(0x[0-9a-fA-F]{1,8})\](.*)$")
-balance_tree_re = re.compile("^([ \|0-9-]*)")
+# Matches lines produced by NS_FormatCodeAddress().
+line_re = re.compile("^(.*#\d+: )(.+)\[(.+) \+(0x[0-9A-Fa-f]+)\](.*)$")
 atos_name_re = re.compile("^(.+) \(in ([^)]+)\) \((.+)\)$")
 
 def fixSymbols(line):
     result = line_re.match(line)
     if result is not None:
-        # before allows preservation of balance trees
-        # after allows preservation of counts
-        (before, file, address, after) = result.groups()
+        (before, fn, file, address, after) = result.groups()
         address = int(address, 16)
 
         if os.path.exists(file) and os.path.isfile(file):
             address += address_adjustment(file)
             info = addressToSymbol(file, address)
 
             # atos output seems to have three forms:
             #   address
@@ -124,19 +115,16 @@ def fixSymbols(line):
                 # Print the first two forms as-is, and transform the third
                 (name, library, fileline) = name_result.groups()
                 # atos demangles, but occasionally it fails.  cxxfilt can mop
                 # up the remaining cases(!), which will begin with '_Z'.
                 if (name.startswith("_Z")):
                     name = cxxfilt(name)
                 info = "%s (%s, in %s)" % (name, fileline, library)
 
-            # throw away the bad symbol, but keep balance tree structure
-            before = balance_tree_re.match(before).groups()[0]
-
             nl = '\n' if line[-1] == '\n' else ''
             return before + info + after + nl
         else:
             sys.stderr.write("Warning: File \"" + file + "\" does not exist.\n")
             return line
     else:
         return line
 
--- a/tools/rb/fix_stack_using_bpsyms.py
+++ b/tools/rb/fix_stack_using_bpsyms.py
@@ -1,14 +1,18 @@
 #!/usr/bin/env python
 
 # 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/.
 
+# This script uses breakpad symbols to post-process the entries produced by
+# NS_FormatCodeAddress(), which on TBPL builds often lack a file name and a
+# line number (and on Linux even the symbol is often bad).
+
 from __future__ import with_statement
 
 import sys
 import os
 import re
 import bisect
 
 def prettyFileName(name):
@@ -107,28 +111,24 @@ def getSymbolFile(file, symbolsDir):
 
 def addressToSymbol(file, address, symbolsDir):
   p = getSymbolFile(file, symbolsDir)
   if p:
     return p.addrToSymbol(address)
   else:
     return ""
 
-line_re = re.compile("^(.*) ?\[([^ ]*) \+(0x[0-9A-F]{1,16})\](.*)$")
-balance_tree_re = re.compile("^([ \|0-9-]*)")
+# Matches lines produced by NS_FormatCodeAddress().
+line_re = re.compile("^(.*#\d+: )(.+)\[(.+) \+(0x[0-9A-Fa-f]+)\](.*)$")
 
 def fixSymbols(line, symbolsDir):
   result = line_re.match(line)
   if result is not None:
-    # before allows preservation of balance trees
-    # after allows preservation of counts
-    (before, file, address, after) = result.groups()
+    (before, fn, file, address, after) = result.groups()
     address = int(address, 16)
-    # throw away the bad symbol, but keep balance tree structure
-    before = balance_tree_re.match(before).groups()[0]
     symbol = addressToSymbol(file, address, symbolsDir)
     if not symbol:
       symbol = "%s + 0x%x" % (os.path.basename(file), address)
     return before + symbol + after + "\n"
   else:
     return line
 
 if __name__ == "__main__":
--- a/tools/rb/make-tree.pl
+++ b/tools/rb/make-tree.pl
@@ -92,21 +92,26 @@ sub read_data($$$) {
           my $sno   = shift(@fields);
           next LINE unless ($obj eq $::opt_object);
      
           my $op  = shift(@fields);
           next LINE unless ($op eq $plus || $op eq $minus);
      
           my $cnt = shift(@fields);
      
-          # Collect the remaining lines to create a stack trace.
+          # Collect the remaining lines to create a stack trace. We need to
+          # filter out the frame numbers so that frames that differ only in
+          # their frame number are considered equivalent. However, we need to
+          # keep a frame number on each line so that the fix*.py scripts can
+          # parse the output. So we set the frame number to 0 for every frame.
           my @stack;
           CALLSITE: while (<$INFILE>) {
               chomp;
               last CALLSITE if (/^$/);
+              $_ =~ s/#\d+: /#00: /;    # replace frame number with 0
               $stack[++$#stack] = $_;
           }
      
           # Reverse the remaining fields to produce the call stack, with the
           # oldest frame at the front of the array.
           if (! $::opt_reverse) {
               @stack = reverse(@stack);
           }
--- a/tools/trace-malloc/lib/nsTraceMalloc.c
+++ b/tools/trace-malloc/lib/nsTraceMalloc.c
@@ -888,17 +888,17 @@ calltree(void **stack, size_t num_stack_
     return NULL;
 }
 
 /*
  * Buffer the stack from top at low index to bottom at high, so that we can
  * reverse it in calltree.
  */
 static void
-stack_callback(void *pc, void *sp, void *closure)
+stack_callback(uint32_t frameNumber, void *pc, void *sp, void *closure)
 {
     stack_buffer_info *info = (stack_buffer_info*) closure;
 
     /*
      * If we run out of buffer, keep incrementing entries so that
      * backtrace can call us again with a bigger buffer.
      */
     if (info->entries < info->size)
--- a/widget/gonk/HwcComposer2D.cpp
+++ b/widget/gonk/HwcComposer2D.cpp
@@ -26,16 +26,21 @@
 #include "mozilla/layers/PLayerTransaction.h"
 #include "mozilla/layers/ShadowLayerUtilsGralloc.h"
 #include "mozilla/layers/TextureHostOGL.h"  // for TextureHostOGL
 #include "mozilla/StaticPtr.h"
 #include "cutils/properties.h"
 #include "gfx2DGlue.h"
 #include "GeckoTouchDispatcher.h"
 
+#ifdef MOZ_ENABLE_PROFILER_SPS
+#include "GeckoProfiler.h"
+#include "ProfilerMarkers.h"
+#endif
+
 #if ANDROID_VERSION >= 17
 #include "libdisplay/FramebufferSurface.h"
 #include "gfxPrefs.h"
 #include "nsThreadUtils.h"
 
 #ifndef HWC_BLIT
 #define HWC_BLIT (HWC_FRAMEBUFFER_TARGET + 1)
 #endif
@@ -65,16 +70,18 @@
 
 using namespace android;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 
 namespace mozilla {
 
 #if ANDROID_VERSION >= 17
+nsecs_t sAndroidInitTime = 0;
+mozilla::TimeStamp sMozInitTime;
 static void
 HookInvalidate(const struct hwc_procs* aProcs)
 {
     HwcComposer2D::GetInstance()->Invalidate();
 }
 
 static void
 HookVsync(const struct hwc_procs* aProcs, int aDisplay,
@@ -149,16 +156,18 @@ HwcComposer2D::Init(hwc_display_t dpy, h
             mRBSwapSupport = !!supported;
         }
     } else {
         mColorFill = false;
         mRBSwapSupport = false;
     }
 
     if (RegisterHwcEventCallback()) {
+        sAndroidInitTime = systemTime(SYSTEM_TIME_MONOTONIC);
+        sMozInitTime = TimeStamp::Now();
         EnableVsync(true);
     }
 #else
     char propValue[PROPERTY_VALUE_MAX];
     property_get("ro.display.colorfill", propValue, "0");
     mColorFill = (atoi(propValue) == 1) ? true : false;
     mRBSwapSupport = true;
 #endif
@@ -224,19 +233,27 @@ HwcComposer2D::RunVsyncEventControl(bool
         HwcDevice* device = (HwcDevice*)GetGonkDisplay()->GetHWCDevice();
         if (device && device->eventControl) {
             device->eventControl(device, HWC_DISPLAY_PRIMARY, HWC_EVENT_VSYNC, aEnable);
         }
     }
 }
 
 void
-HwcComposer2D::Vsync(int aDisplay, int64_t aTimestamp)
+HwcComposer2D::Vsync(int aDisplay, nsecs_t aVsyncTimestamp)
 {
-    GeckoTouchDispatcher::NotifyVsync(aTimestamp);
+#ifdef MOZ_ENABLE_PROFILER_SPS
+    if (profiler_is_active()) {
+      nsecs_t timeSinceInit = aVsyncTimestamp - sAndroidInitTime;
+      TimeStamp vsyncTime = sMozInitTime + TimeDuration::FromMicroseconds(timeSinceInit / 1000);
+      CompositorParent::PostInsertVsyncProfilerMarker(vsyncTime);
+    }
+#endif
+
+    GeckoTouchDispatcher::NotifyVsync(aVsyncTimestamp);
 }
 
 // Called on the "invalidator" thread (run from HAL).
 void
 HwcComposer2D::Invalidate()
 {
     if (!Initialized()) {
         LOGE("HwcComposer2D::Invalidate failed!");
--- a/xpcom/base/CodeAddressService.h
+++ b/xpcom/base/CodeAddressService.h
@@ -84,22 +84,19 @@ class CodeAddressService
     void Replace(const void* aPc, const char* aFunction,
                  const char* aLibrary, ptrdiff_t aLOffset,
                  const char* aFileName, unsigned long aLineNo)
     {
       mPc = aPc;
 
       // Convert "" to nullptr.  Otherwise, make a copy of the name.
       StringAlloc::free(mFunction);
-      mFunction =
-        !aFunction[0] ? nullptr : StringAlloc::copy(aFunction);
+      mFunction = !aFunction[0] ? nullptr : StringAlloc::copy(aFunction);
       StringAlloc::free(mFileName);
-      mFileName =
-        !aFileName[0] ? nullptr : StringAlloc::copy(aFileName);
-
+      mFileName = !aFileName[0] ? nullptr : StringAlloc::copy(aFileName);
 
       mLibrary = aLibrary;
       mLOffset = aLOffset;
       mLineNo = aLineNo;
 
       mInUse = 1;
     }
 
@@ -127,17 +124,18 @@ class CodeAddressService
   size_t mNumCacheMisses;
 
 public:
   CodeAddressService()
     : mEntries(), mNumCacheHits(0), mNumCacheMisses(0)
   {
   }
 
-  void GetLocation(const void* aPc, char* aBuf, size_t aBufLen)
+  void GetLocation(uint32_t aFrameNumber, const void* aPc, char* aBuf,
+                   size_t aBufLen)
   {
     MOZ_ASSERT(DescribeCodeAddressLock::IsLocked());
 
     uint32_t index = HashGeneric(aPc) & kMask;
     MOZ_ASSERT(index < kNumEntries);
     Entry& entry = mEntries[index];
 
     if (!entry.mInUse || entry.mPc != aPc) {
@@ -160,36 +158,19 @@ public:
                     details.filename, details.lineno);
 
     } else {
       mNumCacheHits++;
     }
 
     MOZ_ASSERT(entry.mPc == aPc);
 
-    uintptr_t entryPc = (uintptr_t)(entry.mPc);
-    // Sometimes we get nothing useful.  Just print "???" for the entire entry
-    // so that fix_linux_stack.py doesn't complain about an empty filename.
-    if (!entry.mFunction && !entry.mLibrary[0] && entry.mLOffset == 0) {
-      snprintf(aBuf, aBufLen, "??? 0x%" PRIxPTR, entryPc);
-    } else {
-      // Use "???" for unknown functions.
-      const char* entryFunction = entry.mFunction ? entry.mFunction : "???";
-      if (entry.mFileName) {
-        // On Windows we can get the filename and line number at runtime.
-        snprintf(aBuf, aBufLen, "%s (%s:%u) 0x%" PRIxPTR,
-                 entryFunction, entry.mFileName, entry.mLineNo, entryPc);
-      } else {
-        // On Linux and Mac we cannot get the filename and line number at
-        // runtime, so we print the offset in a form that fix_linux_stack.py and
-        // fix_macosx_stack.py can post-process.
-        snprintf(aBuf, aBufLen, "%s[%s +0x%" PRIXPTR "] 0x%" PRIxPTR,
-                 entryFunction, entry.mLibrary, entry.mLOffset, entryPc);
-      }
-    }
+    NS_FormatCodeAddress(aBuf, aBufLen, aFrameNumber, entry.mPc,
+                         entry.mFunction, entry.mLibrary, entry.mLOffset,
+                         entry.mFileName, entry.mLineNo);
   }
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
   {
     size_t n = aMallocSizeOf(this);
     for (uint32_t i = 0; i < kNumEntries; i++) {
       n += mEntries[i].SizeOfExcludingThis(aMallocSizeOf);
     }
--- a/xpcom/base/nsIMemoryInfoDumper.idl
+++ b/xpcom/base/nsIMemoryInfoDumper.idl
@@ -54,89 +54,39 @@ interface nsIMemoryInfoDumper : nsISuppo
    * @param aFilename The output file.
    *
    * @param aFinishDumping The callback called on completion.
    *
    * @param aFinishDumpingData The environment for the callback.
    *
    * @param aAnonymize Should the reports be anonymized?
    *
-   * Sample output:
+   * Sample output, annotated with comments for explanatory purposes.
    *
    * {
+   *   // The version number of the format, which will be incremented each time
+   *   // backwards-incompatible changes are made. A mandatory integer.
    *   "version": 1
+   *
+   *   // Equal to nsIMemoryReporterManager::hasMozMallocUsableSize. A
+   *   // mandatory boolean.
    *   "hasMozMallocUsableSize": true,
+   *
+   *   // The memory reports. A mandatory array.
    *   "reports": [
+   *     // The properties correspond to the arguments of
+   *     // nsIHandleReportCallback::callback. Every one is mandatory.
    *     {"process":"Main Process (pid 12345)", "path":"explicit/foo/bar",
    *      "kind":1, "units":0, "amount":2000000, "description":"Foo bar."},
    *     {"process":"Main Process (pid 12345)", "path":"heap-allocated",
    *      "kind":1, "units":0, "amount":3000000, "description":"Heap allocated."},
    *     {"process":"Main Process (pid 12345)", "path":"vsize",
    *      "kind":1, "units":0, "amount":10000000, "description":"Vsize."}
    *   ]
    * }
-   *
-   * JSON schema for the output.
-   *
-   * {
-   *   "properties": {
-   *     "version": {
-   *       "type": "integer",
-   *       "description": "Version number of this schema.",
-   *       "required": true
-   *     },
-   *     "hasMozMallocUsableSize": {
-   *       "type": "boolean",
-   *       "description": "nsIMemoryReporterManager::hasMozMallocUsableSize",
-   *       "required": true
-   *     },
-   *     "reports": {
-   *       "type": "array",
-   *       "description": "The memory reports.",
-   *       "required": true
-   *       "minItems": 1,
-   *       "items": {
-   *         "type": "object",
-   *         "properties": {
-   *           "process": {
-   *             "type": "string",
-   *             "description": "nsIMemoryReporter::process",
-   *             "required": true
-   *           },
-   *           "path": {
-   *             "type": "string",
-   *             "description": "nsIMemoryReporter::path",
-   *             "required": true,
-   *             "minLength": 1
-   *           },
-   *           "kind": {
-   *             "type": "integer",
-   *             "description": "nsIMemoryReporter::kind",
-   *             "required": true
-   *           },
-   *           "units": {
-   *             "type": "integer",
-   *             "description": "nsIMemoryReporter::units",
-   *             "required": true
-   *           },
-   *           "amount": {
-   *             "type": "integer",
-   *             "description": "nsIMemoryReporter::amount",
-   *             "required": true
-   *           },
-   *           "description": {
-   *             "type": "string",
-   *             "description": "nsIMemoryReporter::description",
-   *             "required": true
-   *           }
-   *         }
-   *       }
-   *     }
-   *   }
-   * }
    */
   void dumpMemoryReportsToNamedFile(in AString aFilename,
                                     in nsIFinishDumpingCallback aFinishDumping,
                                     in nsISupports aFinishDumpingData,
                                     in boolean aAnonymize);
 
   /**
    * Similar to dumpMemoryReportsToNamedFile, this method dumps gzipped memory
--- a/xpcom/base/nsMemoryInfoDumper.cpp
+++ b/xpcom/base/nsMemoryInfoDumper.cpp
@@ -425,38 +425,16 @@ MakeFilename(const char* aPrefix, const 
              int aPid, const char* aSuffix, nsACString& aResult)
 {
   aResult = nsPrintfCString("%s-%s-%d.%s",
                             aPrefix,
                             NS_ConvertUTF16toUTF8(aIdentifier).get(),
                             aPid, aSuffix);
 }
 
-#ifdef MOZ_DMD
-struct DMDWriteState
-{
-  static const size_t kBufSize = 4096;
-  char mBuf[kBufSize];
-  nsRefPtr<nsGZFileWriter> mGZWriter;
-
-  DMDWriteState(nsGZFileWriter* aGZWriter)
-    : mGZWriter(aGZWriter)
-  {
-  }
-};
-
-static void
-DMDWrite(void* aState, const char* aFmt, va_list ap)
-{
-  DMDWriteState* state = (DMDWriteState*)aState;
-  vsnprintf(state->mBuf, state->kBufSize, aFmt, ap);
-  unused << state->mGZWriter->Write(state->mBuf);
-}
-#endif
-
 // This class wraps GZFileWriter so it can be used with JSONWriter, overcoming
 // the following two problems:
 // - It provides a JSONWriterFunc::Write() that calls nsGZFileWriter::Write().
 // - It can be stored as a UniquePtr, whereas nsGZFileWriter is refcounted.
 class GZWriterWrapper : public JSONWriteFunc
 {
 public:
   explicit GZWriterWrapper(nsGZFileWriter* aGZWriter)
@@ -786,20 +764,20 @@ nsresult
 nsMemoryInfoDumper::OpenDMDFile(const nsAString& aIdentifier, int aPid,
                                 FILE** aOutFile)
 {
   if (!dmd::IsRunning()) {
     *aOutFile = nullptr;
     return NS_OK;
   }
 
-  // Create a filename like dmd-<identifier>-<pid>.txt.gz, which will be used
+  // Create a filename like dmd-<identifier>-<pid>.json.gz, which will be used
   // if DMD is enabled.
   nsCString dmdFilename;
-  MakeFilename("dmd", aIdentifier, aPid, "txt.gz", dmdFilename);
+  MakeFilename("dmd", aIdentifier, aPid, "json.gz", dmdFilename);
 
   // Open a new DMD file named |dmdFilename| in NS_OS_TEMP_DIR for writing,
   // and dump DMD output to it.  This must occur after the memory reporters
   // have been run (above), but before the memory-reports file has been
   // renamed (so scripts can detect the DMD file, if present).
 
   nsresult rv;
   nsCOMPtr<nsIFile> dmdFile;
@@ -821,25 +799,24 @@ nsMemoryInfoDumper::OpenDMDFile(const ns
   dmd::StatusMsg("opened %s for writing\n", path.get());
 
   return rv;
 }
 
 nsresult
 nsMemoryInfoDumper::DumpDMDToFile(FILE* aFile)
 {
-  nsRefPtr<nsGZFileWriter> dmdWriter = new nsGZFileWriter();
-  nsresult rv = dmdWriter->InitANSIFileDesc(aFile);
+  nsRefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
+  nsresult rv = gzWriter->InitANSIFileDesc(aFile);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Dump DMD's memory reports analysis to the file.
-  DMDWriteState state(dmdWriter);
-  dmd::Writer w(DMDWrite, &state);
-  dmd::AnalyzeReports(w);
+  JSONWriter jsonWriter(MakeUnique<GZWriterWrapper>(gzWriter));
+  dmd::AnalyzeReports(jsonWriter);
 
-  rv = dmdWriter->Finish();
+  rv = gzWriter->Finish();
   NS_WARN_IF(NS_FAILED(rv));
   return rv;
 }
 #endif  // MOZ_DMD
 
--- a/xpcom/base/nsStackWalk.cpp
+++ b/xpcom/base/nsStackWalk.cpp
@@ -8,16 +8,20 @@
 
 #include "mozilla/Assertions.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/StackWalk.h"
 #include "nsStackWalkPrivate.h"
 
 #include "nsStackWalk.h"
 
+#ifdef XP_WIN
+#define snprintf _snprintf
+#endif
+
 using namespace mozilla;
 
 // The presence of this address is the stack must stop the stack walk. If
 // there is no such address, the structure will be {nullptr, true}.
 struct CriticalAddress
 {
   void* mAddr;
   bool mInit;
@@ -49,17 +53,17 @@ static CriticalAddress gCriticalAddress;
 
 typedef void
 malloc_logger_t(uint32_t aType,
                 uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3,
                 uintptr_t aResult, uint32_t aNumHotFramesToSkip);
 extern malloc_logger_t* malloc_logger;
 
 static void
-stack_callback(void* aPc, void* aSp, void* aClosure)
+stack_callback(uint32_t aFrameNumber, void* aPc, void* aSp, void* aClosure)
 {
   const char* name = static_cast<char*>(aClosure);
   Dl_info info;
 
   // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The
   // stack shows up as having two pthread_cond_wait$UNIX2003 frames. The
   // correct one is the first that we find on our way up, so the
   // following check for gCriticalAddress.mAddr is critical.
@@ -607,17 +611,17 @@ NS_StackWalk(NS_WalkStackCallback aCallb
 
     ::CloseHandle(data.eventStart);
     ::CloseHandle(data.eventEnd);
   }
 
   ::CloseHandle(myThread);
 
   for (uint32_t i = 0; i < data.pc_count; ++i) {
-    (*aCallback)(data.pcs[i], data.sps[i], aClosure);
+    (*aCallback)(i + 1, data.pcs[i], data.sps[i], aClosure);
   }
 
   return data.pc_count == 0 ? NS_ERROR_FAILURE : NS_OK;
 }
 
 
 static BOOL CALLBACK
 callbackEspecial64(
@@ -822,48 +826,16 @@ NS_DescribeCodeAddress(void* aPC, nsCode
                 sizeof(aDetails->function));
     aDetails->foffset = static_cast<ptrdiff_t>(displacement);
   }
 
   LeaveCriticalSection(&gDbgHelpCS); // release our lock
   return NS_OK;
 }
 
-EXPORT_XPCOM_API(nsresult)
-NS_FormatCodeAddressDetails(void* aPC, const nsCodeAddressDetails* aDetails,
-                            char* aBuffer, uint32_t aBufferSize)
-{
-  if (aDetails->function[0]) {
-    _snprintf(aBuffer, aBufferSize, "%s+0x%08lX [%s +0x%016lX]",
-              aDetails->function, aDetails->foffset,
-              aDetails->library, aDetails->loffset);
-  } else if (aDetails->library[0]) {
-    _snprintf(aBuffer, aBufferSize, "UNKNOWN [%s +0x%016lX]",
-              aDetails->library, aDetails->loffset);
-  } else {
-    _snprintf(aBuffer, aBufferSize, "UNKNOWN 0x%016lX", aPC);
-  }
-
-  aBuffer[aBufferSize - 1] = '\0';
-
-  uint32_t len = strlen(aBuffer);
-  if (aDetails->filename[0]) {
-    _snprintf(aBuffer + len, aBufferSize - len, " (%s, line %d)\n",
-              aDetails->filename, aDetails->lineno);
-  } else {
-    aBuffer[len] = '\n';
-    if (++len != aBufferSize) {
-      aBuffer[len] = '\0';
-    }
-  }
-  aBuffer[aBufferSize - 2] = '\n';
-  aBuffer[aBufferSize - 1] = '\0';
-  return NS_OK;
-}
-
 // i386 or PPC Linux stackwalking code
 #elif HAVE_DLADDR && (HAVE__UNWIND_BACKTRACE || NSSTACKWALK_SUPPORTS_LINUX || NSSTACKWALK_SUPPORTS_MACOSX)
 
 #include <stdlib.h>
 #include <string.h>
 #include "nscore.h"
 #include <stdio.h>
 #include "plstr.h"
@@ -942,18 +914,18 @@ FramePointerStackWalk(NS_WalkStackCallba
     if (IsCriticalAddress(pc)) {
       return NS_ERROR_UNEXPECTED;
     }
     if (--skip < 0) {
       // Assume that the SP points to the BP of the function
       // it called. We can't know the exact location of the SP
       // but this should be sufficient for our use the SP
       // to order elements on the stack.
-      (*aCallback)(pc, bp, aClosure);
       numFrames++;
+      (*aCallback)(numFrames, pc, bp, aClosure);
       if (aMaxFrames != 0 && numFrames == aMaxFrames) {
         break;
       }
     }
     bp = next;
   }
   return numFrames == 0 ? NS_ERROR_FAILURE : NS_OK;
 }
@@ -986,17 +958,16 @@ NS_StackWalk(NS_WalkStackCallback aCallb
   void* stackEnd;
 #if HAVE___LIBC_STACK_END
   stackEnd = __libc_stack_end;
 #else
   stackEnd = reinterpret_cast<void*>(-1);
 #endif
   return FramePointerStackWalk(aCallback, aSkipFrames, aMaxFrames,
                                aClosure, bp, stackEnd);
-
 }
 
 #elif defined(HAVE__UNWIND_BACKTRACE)
 
 // libgcc_s.so symbols _Unwind_Backtrace@@GCC_3.3 and _Unwind_GetIP@@GCC_3.0
 #include <unwind.h>
 
 struct unwind_info
@@ -1018,18 +989,18 @@ unwind_callback(struct _Unwind_Context* 
   if (IsCriticalAddress(pc)) {
     info->isCriticalAbort = true;
     // We just want to stop the walk, so any error code will do.  Using
     // _URC_NORMAL_STOP would probably be the most accurate, but it is not
     // defined on Android for ARM.
     return _URC_FOREIGN_EXCEPTION_CAUGHT;
   }
   if (--info->skip < 0) {
-    (*info->callback)(pc, nullptr, info->closure);
     info->numFrames++;
+    (*info->callback)(info->numFrames, pc, nullptr, info->closure);
     if (info->maxFrames != 0 && info->numFrames == info->maxFrames) {
       // Again, any error code that stops the walk will do.
       return _URC_FOREIGN_EXCEPTION_CAUGHT;
     }
   }
   return _URC_NO_REASON;
 }
 
@@ -1097,34 +1068,16 @@ NS_DescribeCodeAddress(void* aPC, nsCode
     // Just use the mangled symbol if demangling failed.
     PL_strncpyz(aDetails->function, symbol, sizeof(aDetails->function));
   }
 
   aDetails->foffset = (char*)aPC - (char*)info.dli_saddr;
   return NS_OK;
 }
 
-EXPORT_XPCOM_API(nsresult)
-NS_FormatCodeAddressDetails(void* aPC, const nsCodeAddressDetails* aDetails,
-                            char* aBuffer, uint32_t aBufferSize)
-{
-  if (!aDetails->library[0]) {
-    snprintf(aBuffer, aBufferSize, "UNKNOWN %p\n", aPC);
-  } else if (!aDetails->function[0]) {
-    snprintf(aBuffer, aBufferSize, "UNKNOWN [%s +0x%08" PRIXPTR "]\n",
-             aDetails->library, aDetails->loffset);
-  } else {
-    snprintf(aBuffer, aBufferSize, "%s+0x%08" PRIXPTR
-             " [%s +0x%08" PRIXPTR "]\n",
-             aDetails->function, aDetails->foffset,
-             aDetails->library, aDetails->loffset);
-  }
-  return NS_OK;
-}
-
 #else // unsupported platform.
 
 EXPORT_XPCOM_API(nsresult)
 NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
              uint32_t aMaxFrames, void* aClosure, uintptr_t aThread,
              void* aPlatformData)
 {
   MOZ_ASSERT(!aThread);
@@ -1148,17 +1101,49 @@ NS_DescribeCodeAddress(void* aPC, nsCode
   aDetails->loffset = 0;
   aDetails->filename[0] = '\0';
   aDetails->lineno = 0;
   aDetails->function[0] = '\0';
   aDetails->foffset = 0;
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
-EXPORT_XPCOM_API(nsresult)
-NS_FormatCodeAddressDetails(void* aPC, const nsCodeAddressDetails* aDetails,
-                            char* aBuffer, uint32_t aBufferSize)
+#endif
+
+EXPORT_XPCOM_API(void)
+NS_FormatCodeAddressDetails(char* aBuffer, uint32_t aBufferSize,
+                            uint32_t aFrameNumber, void* aPC,
+                            const nsCodeAddressDetails* aDetails)
 {
-  aBuffer[0] = '\0';
-  return NS_ERROR_NOT_IMPLEMENTED;
+  NS_FormatCodeAddress(aBuffer, aBufferSize,
+                       aFrameNumber, aPC, aDetails->function,
+                       aDetails->library, aDetails->loffset,
+                       aDetails->filename, aDetails->lineno);
 }
 
-#endif
+EXPORT_XPCOM_API(void)
+NS_FormatCodeAddress(char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber,
+                     const void* aPC, const char* aFunction,
+                     const char* aLibrary, ptrdiff_t aLOffset,
+                     const char* aFileName, uint32_t aLineNo)
+{
+  const char* function = aFunction && aFunction[0] ? aFunction : "???";
+  if (aFileName && aFileName[0]) {
+    // We have a filename and (presumably) a line number. Use them.
+    snprintf(aBuffer, aBufferSize,
+             "#%02u: %s (%s:%u)",
+             aFrameNumber, function, aFileName, aLineNo);
+  } else if (aLibrary && aLibrary[0]) {
+    // We have no filename, but we do have a library name. Use it and the
+    // library offset, and print them in a way that scripts like
+    // fix_{linux,macosx}_stacks.py can easily post-process.
+    snprintf(aBuffer, aBufferSize,
+             "#%02u: %s[%s +0x%" PRIxPTR "]",
+             aFrameNumber, function, aLibrary, aLOffset);
+  } else {
+    // We have nothing useful to go on. (The format string is split because
+    // '??)' is a trigraph and causes a warning, sigh.)
+    snprintf(aBuffer, aBufferSize,
+             "#%02u: ??? (???:???" ")",
+             aFrameNumber);
+  }
+}
+
--- a/xpcom/base/nsStackWalk.h
+++ b/xpcom/base/nsStackWalk.h
@@ -13,21 +13,30 @@
 
 #include "nscore.h"
 #include <stdint.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-// aSP will be the best approximation possible of what the stack pointer will be
-// pointing to when the execution returns to executing that at that PC.
-// If no approximation can be made it will be nullptr.
+/**
+ * The callback for NS_StackWalk.
+ *
+ * @param aFrameNumber  The frame number (starts at 1, not 0).
+ * @param aPC           The program counter value.
+ * @param aSP           The best approximation possible of what the stack
+ *                      pointer will be pointing to when the execution returns
+ *                      to executing that at aPC. If no approximation can
+ *                      be made it will be nullptr.
+ * @param aClosure      Extra data passed in via NS_StackWalk().
+ */
 typedef void
-(*NS_WalkStackCallback)(void* aPC, void* aSP, void* aClosure);
+(*NS_WalkStackCallback)(uint32_t aFrameNumber, void* aPC, void* aSP,
+                        void* aClosure);
 
 /**
  * Call aCallback for the C/C++ stack frames on the current thread, from
  * the caller of NS_StackWalk to main (or above).
  *
  * @param aCallback    Callback function, called once per frame.
  * @param aSkipFrames  Number of initial frames to skip.  0 means that
  *                     the first callback will be for the caller of
@@ -104,27 +113,58 @@ NS_DescribeCodeAddress(void* aPC, nsCode
 
 /**
  * Format the information about a code address in a format suitable for
  * stack traces on the current platform.  When available, this string
  * should contain the function name, source file, and line number.  When
  * these are not available, library and offset should be reported, if
  * possible.
  *
- * @param aPC         The code address.
- * @param aDetails    The value filled in by NS_DescribeCodeAddress(aPC).
- * @param aBuffer     A string to be filled in with the description.
- *                    The string will always be null-terminated.
- * @param aBufferSize The size, in bytes, of aBuffer, including
- *                    room for the terminating null.  If the information
- *                    to be printed would be larger than aBuffer, it
- *                    will be truncated so that aBuffer[aBufferSize-1]
- *                    is the terminating null.
+ * Note that this output is parsed by several scripts including the fix*.py and
+ * make-tree.pl scripts in tools/rb/. It should only be change with care, and
+ * in conjunction with those scripts.
+ *
+ * @param aBuffer      A string to be filled in with the description.
+ *                     The string will always be null-terminated.
+ * @param aBufferSize  The size, in bytes, of aBuffer, including
+ *                     room for the terminating null.  If the information
+ *                     to be printed would be larger than aBuffer, it
+ *                     will be truncated so that aBuffer[aBufferSize-1]
+ *                     is the terminating null.
+ * @param aFrameNumber The frame number.
+ * @param aPC          The code address.
+ * @param aFunction    The function name. Possibly null or the empty string.
+ * @param aLibrary     The library name. Possibly null or the empty string.
+ * @param aLOffset     The library offset.
+ * @param aFileName    The filename. Possibly null or the empty string.
+ * @param aLineNo      The line number. Possibly zero.
  */
-XPCOM_API(nsresult)
-NS_FormatCodeAddressDetails(void* aPC, const nsCodeAddressDetails* aDetails,
-                            char* aBuffer, uint32_t aBufferSize);
+XPCOM_API(void)
+NS_FormatCodeAddress(char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber,
+                     const void* aPC, const char* aFunction,
+                     const char* aLibrary, ptrdiff_t aLOffset,
+                     const char* aFileName, uint32_t aLineNo);
+
+/**
+ * Format the information about a code address in the same fashion as
+ * NS_FormatCodeAddress.
+ *
+ * @param aBuffer      A string to be filled in with the description.
+ *                     The string will always be null-terminated.
+ * @param aBufferSize  The size, in bytes, of aBuffer, including
+ *                     room for the terminating null.  If the information
+ *                     to be printed would be larger than aBuffer, it
+ *                     will be truncated so that aBuffer[aBufferSize-1]
+ *                     is the terminating null.
+ * @param aFrameNumber The frame number.
+ * @param aPC          The code address.
+ * @param aDetails     The value filled in by NS_DescribeCodeAddress(aPC).
+ */
+XPCOM_API(void)
+NS_FormatCodeAddressDetails(char* aBuffer, uint32_t aBufferSize,
+                            uint32_t aFrameNumber, void* aPC,
+                            const nsCodeAddressDetails* aDetails);
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif /* !defined(nsStackWalk_h_) */
--- a/xpcom/base/nsTraceRefcnt.cpp
+++ b/xpcom/base/nsTraceRefcnt.cpp
@@ -924,35 +924,38 @@ InitTraceLog()
 }
 
 #endif
 
 extern "C" {
 
 #ifdef MOZ_STACKWALKING
 static void
-PrintStackFrame(void* aPC, void* aSP, void* aClosure)
+PrintStackFrame(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure)
 {
   FILE* stream = (FILE*)aClosure;
   nsCodeAddressDetails details;
   char buf[1024];
 
   NS_DescribeCodeAddress(aPC, &details);
-  NS_FormatCodeAddressDetails(aPC, &details, buf, sizeof(buf));
-  fputs(buf, stream);
+  NS_FormatCodeAddressDetails(buf, sizeof(buf), aFrameNumber, aPC, &details);
+  fprintf(stream, "%s\n", buf);
+  fflush(stream);
 }
 
 static void
-PrintStackFrameCached(void* aPC, void* aSP, void* aClosure)
+PrintStackFrameCached(uint32_t aFrameNumber, void* aPC, void* aSP,
+                      void* aClosure)
 {
   auto stream = static_cast<FILE*>(aClosure);
   static const size_t buflen = 1024;
   char buf[buflen];
-  gCodeAddressService->GetLocation(aPC, buf, buflen);
+  gCodeAddressService->GetLocation(aFrameNumber, aPC, buf, buflen);
   fprintf(stream, "    %s\n", buf);
+  fflush(stream);
 }
 #endif
 
 }
 
 void
 nsTraceRefcnt::WalkTheStack(FILE* aStream)
 {
--- a/xpcom/build/LateWriteChecks.cpp
+++ b/xpcom/build/LateWriteChecks.cpp
@@ -74,17 +74,17 @@ public:
     mFile = nullptr;
   }
 private:
   FILE* mFile;
   SHA1Sum mSHA1;
 };
 
 static void
-RecordStackWalker(void* aPC, void* aSP, void* aClosure)
+RecordStackWalker(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure)
 {
   std::vector<uintptr_t>* stack =
     static_cast<std::vector<uintptr_t>*>(aClosure);
   stack->push_back(reinterpret_cast<uintptr_t>(aPC));
 }
 
 /**************************** Late-Write Observer  ****************************/
 
--- a/xpcom/glue/BlockingResourceBase.cpp
+++ b/xpcom/glue/BlockingResourceBase.cpp
@@ -43,17 +43,18 @@ const char* const BlockingResourceBase::
 #ifdef DEBUG
 
 PRCallOnceType BlockingResourceBase::sCallOnce;
 unsigned BlockingResourceBase::sResourceAcqnChainFrontTPI = (unsigned)-1;
 BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector;
 
 
 void
-BlockingResourceBase::StackWalkCallback(void* aPc, void* aSp, void* aClosure)
+BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber, void* aPc,
+                                        void* aSp, void* aClosure)
 {
 #ifndef MOZ_CALLSTACK_DISABLED
   AcquisitionState* state = (AcquisitionState*)aClosure;
   state->AppendElement(aPc);
 #endif
 }
 
 void
@@ -62,18 +63,17 @@ BlockingResourceBase::GetStackTrace(Acqu
 #ifndef MOZ_CALLSTACK_DISABLED
   // Skip this function and the calling function.
   const uint32_t kSkipFrames = 2;
 
   aState.Clear();
 
   // NB: Ignore the return value, there's nothing useful we can do if this
   //     this fails.
-  NS_StackWalk(StackWalkCallback, kSkipFrames,
-               24, &aState, 0, nullptr);
+  NS_StackWalk(StackWalkCallback, kSkipFrames, 24, &aState, 0, nullptr);
 #endif
 }
 
 /**
  * PrintCycle
  * Append to |aOut| detailed information about the circular
  * dependency in |aCycle|.  Returns true if it *appears* that this
  * cycle may represent an imminent deadlock, but this is merely a
@@ -180,17 +180,17 @@ BlockingResourceBase::Print(nsACString& 
 #ifdef MOZ_CALLSTACK_DISABLED
   fputs("  [stack trace unavailable]\n", stderr);
 #else
   const AcquisitionState& state = acquired ? mAcquired : mFirstSeen;
 
   WalkTheStackCodeAddressService addressService;
 
   for (uint32_t i = 0; i < state.Length(); i++) {
-    const size_t kMaxLength = 4096;
+    const size_t kMaxLength = 1024;
     char buffer[kMaxLength];
     addressService.GetLocation(state[i], buffer, kMaxLength);
     const char* fmt = "    %s\n";
     aOut += nsPrintfCString(fmt, buffer);
     fprintf(stderr, fmt, buffer);
   }
 
 #endif
--- a/xpcom/glue/BlockingResourceBase.h
+++ b/xpcom/glue/BlockingResourceBase.h
@@ -314,17 +314,18 @@ private:
   /**
    * Shutdown
    * Free static members.
    *
    * *NOT* thread safe.
    */
   static void Shutdown();
 
-  static void StackWalkCallback(void* aPc, void* aSp, void* aClosure);
+  static void StackWalkCallback(uint32_t aFrameNumber, void* aPc,
+                                void* aSp, void* aClosure);
   static void GetStackTrace(AcquisitionState& aState);
 
 #  ifdef MOZILLA_INTERNAL_API
   // so it can call BlockingResourceBase::Shutdown()
   friend void LogTerm();
 #  endif  // ifdef MOZILLA_INTERNAL_API
 
 #else  // non-DEBUG implementation
--- a/xpcom/threads/HangMonitor.cpp
+++ b/xpcom/threads/HangMonitor.cpp
@@ -109,17 +109,17 @@ Crash()
                                      NS_LITERAL_CSTRING("1"));
 #endif
 
   NS_RUNTIMEABORT("HangMonitor triggered");
 }
 
 #ifdef REPORT_CHROME_HANGS
 static void
-ChromeStackWalker(void* aPC, void* aSP, void* aClosure)
+ChromeStackWalker(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure)
 {
   MOZ_ASSERT(aClosure);
   std::vector<uintptr_t>* stack =
     static_cast<std::vector<uintptr_t>*>(aClosure);
   if (stack->size() == MAX_CALL_STACK_PCS) {
     return;
   }
   MOZ_ASSERT(stack->size() < MAX_CALL_STACK_PCS);