Merge inbound to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 18 Apr 2014 09:08:59 -0400
changeset 197729 45ba19361b97d3154fa8dc26b707f48fab65fa99
parent 197693 bf19c003ffc3bf08f82cf39cda37834fe13c6823 (current diff)
parent 197728 93d3946fd9e0f292ee90a55964d0b11e56e8992d (diff)
child 197757 e256e72a2174b01f1f430ae5b44878643b0a25f7
child 197776 a6a7df40f00ab6fb260b9ff1415784e5add5b741
child 197788 e5bffd7d60c5780f300425e9f55bcc55c9d80eae
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone31.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 inbound to m-c.
--- a/configure.in
+++ b/configure.in
@@ -4816,18 +4816,21 @@ then
     dnl ========================================================
     MOZ_ARG_DISABLE_BOOL(gio,
     [  --disable-gio           Disable GIO support],
         MOZ_ENABLE_GIO=,
         MOZ_ENABLE_GIO=force)
 
     if test "$MOZ_ENABLE_GIO" -a "$MOZ_ENABLE_GTK"
     then
-        PKG_CHECK_MODULES(_GTKCHECK, gtk+-2.0 >= 2.14, ,
-                          [AC_MSG_ERROR([* * * Could not find gtk+-2.0 > 2.14. Required for build with gio.])])
+        if test "$MOZ_ENABLE_GTK2"
+        then
+            PKG_CHECK_MODULES(_GTKCHECK, gtk+-2.0 >= 2.14, ,
+                              [AC_MSG_ERROR([* * * Could not find gtk+-2.0 > 2.14. Required for build with gio.])])
+        fi
         PKG_CHECK_MODULES(MOZ_GIO, gio-2.0 >= $GIO_VERSION,[
             MOZ_GIO_LIBS=`echo $MOZ_GIO_LIBS | sed 's/-llinc\>//'`
             MOZ_ENABLE_GIO=1
             AC_DEFINE(MOZ_ENABLE_GIO)
         ],[
             if test "$MOZ_ENABLE_GIO" = "force"
             then
                 AC_MSG_ERROR([* * * Could not find gio-2.0 >= $GIO_VERSION])
--- a/content/base/test/csp/test_bug949549.html
+++ b/content/base/test/csp/test_bug949549.html
@@ -15,22 +15,22 @@
   // Ensure that `setRequestContext` doesn't throw with app:// URLs
 
   const csp = SpecialPowers.Cc["@mozilla.org/contentsecuritypolicy;1"]
               .createInstance(SpecialPowers.Ci.nsIContentSecurityPolicy);
 
   const gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
 
   SimpleTest.waitForExplicitFinish();
-  var launchableValue, app;
+  var app;
 
   function setupTest() {
     // We have to install an app in order for the app URL to be valid
     // (otherwise we get a "DummyChannel" that throws NS_NOT_IMPLEMENTED)
-    launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.addPermission("webapps-manage", true, document);
     SpecialPowers.autoConfirmAppInstall(function () {
       let req = navigator.mozApps.install(gManifestURL);
       req.onsuccess = function () {
         app = this.result;
         runTest();
       }
     });
@@ -49,17 +49,16 @@
     } catch(e) {
       ok(false, "setRequestContext throws");
     }
 
     cleanup()
   }
 
   function cleanup() {
-    SpecialPowers.setAllAppsLaunchable(launchableValue);
     let req = navigator.mozApps.mgmt.uninstall(app);
     req.onsuccess = function () {
       SimpleTest.finish();
     };
   }
 
   setupTest();
 </script>
--- a/content/media/AudioNodeEngine.h
+++ b/content/media/AudioNodeEngine.h
@@ -39,16 +39,22 @@ public:
 
   struct Storage {
     Storage()
     {
       mDataToFree = nullptr;
       mSampleData = nullptr;
     }
     ~Storage() { free(mDataToFree); }
+    size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+    {
+      // NB: mSampleData might not be owned, if it is it just points to
+      //     mDataToFree.
+      return aMallocSizeOf(mDataToFree);
+    }
     void* mDataToFree;
     const float* mSampleData;
   };
 
   /**
    * This can be called on any thread.
    */
   uint32_t GetChannels() const { return mContents.Length(); }
@@ -69,19 +75,25 @@ public:
     s->mSampleData = aData;
   }
 
   /**
    * Put this object into an error state where there are no channels.
    */
   void Clear() { mContents.Clear(); }
 
-  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+  virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
   {
-    return mContents.SizeOfExcludingThis(aMallocSizeOf);
+    size_t amount = ThreadSharedObject::SizeOfExcludingThis(aMallocSizeOf);
+    amount += mContents.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < mContents.Length(); i++) {
+      amount += mContents[i].SizeOfExcludingThis(aMallocSizeOf);
+    }
+
+    return amount;
   }
 
 private:
   AutoFallibleTArray<Storage,2> mContents;
 };
 
 /**
  * Allocates an AudioChunk with fresh buffers of WEBAUDIO_BLOCK_SIZE float samples.
@@ -323,16 +335,35 @@ public:
     MOZ_ASSERT(mNode != nullptr);
     mNodeMutex.AssertCurrentThreadOwns();
     mNode = nullptr;
   }
 
   uint16_t InputCount() const { return mInputCount; }
   uint16_t OutputCount() const { return mOutputCount; }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    // NB: |mNode| is tracked separately so it is excluded here.
+    return 0;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  void SizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+                           AudioNodeSizes& aUsage) const
+  {
+    aUsage.mEngine = SizeOfIncludingThis(aMallocSizeOf);
+    aUsage.mDomNode = mNode->SizeOfIncludingThis(aMallocSizeOf);
+    aUsage.mNodeType = mNode->NodeType();
+  }
+
 private:
   dom::AudioNode* mNode;
   Mutex mNodeMutex;
   const uint16_t mInputCount;
   const uint16_t mOutputCount;
 };
 
 }
--- a/content/media/AudioNodeStream.cpp
+++ b/content/media/AudioNodeStream.cpp
@@ -25,16 +25,54 @@ namespace mozilla {
  * Note: This must be a different value than MEDIA_STREAM_DEST_TRACK_ID
  */
 
 AudioNodeStream::~AudioNodeStream()
 {
   MOZ_COUNT_DTOR(AudioNodeStream);
 }
 
+size_t
+AudioNodeStream::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = 0;
+
+  // Not reported:
+  // - mEngine
+
+  amount += ProcessedMediaStream::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mLastChunks.SizeOfExcludingThis(aMallocSizeOf);
+  for (size_t i = 0; i < mLastChunks.Length(); i++) {
+    // NB: This is currently unshared only as there are instances of
+    //     double reporting in DMD otherwise.
+    amount += mLastChunks[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+  }
+
+  return amount;
+}
+
+size_t
+AudioNodeStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+void
+AudioNodeStream::SizeOfAudioNodesIncludingThis(MallocSizeOf aMallocSizeOf,
+                                               AudioNodeSizes& aUsage) const
+{
+  // Explicitly separate out the stream memory.
+  aUsage.mStream = SizeOfIncludingThis(aMallocSizeOf);
+
+  if (mEngine) {
+    // This will fill out the rest of |aUsage|.
+    mEngine->SizeOfIncludingThis(aMallocSizeOf, aUsage);
+  }
+}
+
 void
 AudioNodeStream::SetStreamTimeParameter(uint32_t aIndex, AudioContext* aContext,
                                         double aStreamTime)
 {
   class Message : public ControlMessage {
   public:
     Message(AudioNodeStream* aStream, uint32_t aIndex, MediaStream* aRelativeToStream,
             double aStreamTime)
--- a/content/media/AudioNodeStream.h
+++ b/content/media/AudioNodeStream.h
@@ -150,16 +150,22 @@ public:
                                       double aSeconds);
   /**
    * Get the destination stream time in seconds corresponding to a position on
    * this stream.
    */
   double DestinationTimeFromTicks(AudioNodeStream* aDestination,
                                   TrackTicks aPosition);
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
+  void SizeOfAudioNodesIncludingThis(MallocSizeOf aMallocSizeOf,
+                                     AudioNodeSizes& aUsage) const;
+
 protected:
   void AdvanceOutputSegment();
   void FinishOutput();
   void AccumulateInputChunk(uint32_t aInputIndex, const AudioChunk& aChunk,
                             AudioChunk* aBlock,
                             nsTArray<float>* aDownmixBuffer);
   void UpMixDownMixChunk(const AudioChunk* aChunk, uint32_t aOutputChannelCount,
                          nsTArray<const void*>& aOutputChannels,
--- a/content/media/AudioSegment.cpp
+++ b/content/media/AudioSegment.cpp
@@ -170,17 +170,17 @@ AudioSegment::WriteTo(uint64_t aID, Audi
     uint32_t frames = c.mDuration;
 
     // If we have written data in the past, or we have real (non-silent) data
     // to write, we can proceed. Otherwise, it means we just started the
     // AudioStream, and we don't have real data to write to it (just silence).
     // To avoid overbuffering in the AudioStream, we simply drop the silence,
     // here. The stream will underrun and output silence anyways.
     if (c.mBuffer || aOutput->GetWritten()) {
-      if (c.mBuffer) {
+      if (c.mBuffer && c.mBufferFormat != AUDIO_FORMAT_SILENCE) {
         channelData.SetLength(c.mChannelData.Length());
         for (uint32_t i = 0; i < channelData.Length(); ++i) {
           channelData[i] = c.mChannelData[i];
         }
 
         if (channelData.Length() < outputChannels) {
           // Up-mix. Note that this might actually make channelData have more
           // than outputChannels temporarily.
--- a/content/media/AudioSegment.h
+++ b/content/media/AudioSegment.h
@@ -14,21 +14,38 @@
 #include "mozilla/TimeStamp.h"
 #endif
 
 namespace mozilla {
 
 template<typename T>
 class SharedChannelArrayBuffer : public ThreadSharedObject {
 public:
-  SharedChannelArrayBuffer(nsTArray<nsTArray<T>>* aBuffers)
+  SharedChannelArrayBuffer(nsTArray<nsTArray<T> >* aBuffers)
   {
     mBuffers.SwapElements(*aBuffers);
   }
-  nsTArray<nsTArray<T>> mBuffers;
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = 0;
+    amount += mBuffers.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < mBuffers.Length(); i++) {
+      amount += mBuffers[i].SizeOfExcludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  nsTArray<nsTArray<T> > mBuffers;
 };
 
 class AudioStream;
 class AudioMixer;
 
 /**
  * For auto-arrays etc, guess this as the common number of channels.
  */
@@ -109,16 +126,37 @@ struct AudioChunk {
     mBuffer = nullptr;
     mChannelData.Clear();
     mDuration = aDuration;
     mVolume = 1.0f;
     mBufferFormat = AUDIO_FORMAT_SILENCE;
   }
   int ChannelCount() const { return mChannelData.Length(); }
 
+  size_t SizeOfExcludingThisIfUnshared(MallocSizeOf aMallocSizeOf) const
+  {
+    return SizeOfExcludingThis(aMallocSizeOf, true);
+  }
+
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf, bool aUnshared) const
+  {
+    size_t amount = 0;
+
+    // Possibly owned:
+    // - mBuffer - Can hold data that is also in the decoded audio queue. If it
+    //             is not shared, or unshared == false it gets counted.
+    if (mBuffer && (!aUnshared || !mBuffer->IsShared())) {
+      amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    // Memory in the array is owned by mBuffer.
+    amount += mChannelData.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
   TrackTicks mDuration; // in frames within the buffer
   nsRefPtr<ThreadSharedObject> mBuffer; // the buffer object whose lifetime is managed; null means data is all zeroes
   nsTArray<const void*> mChannelData; // one pointer per channel; empty if and only if mBuffer is null
   float mVolume; // volume multiplier to apply (1.0f if mBuffer is nonnull)
   SampleFormat mBufferFormat; // format of frames in mBuffer (only meaningful if mBuffer is nonnull)
 #ifdef MOZILLA_INTERNAL_API
   mozilla::TimeStamp mTimeStamp;           // time at which this has been fetched from the MediaEngine
 #endif
@@ -226,13 +264,18 @@ public:
 
   int ChannelCount() {
     NS_WARN_IF_FALSE(!mChunks.IsEmpty(),
         "Cannot query channel count on a AudioSegment with no chunks.");
     return mChunks.IsEmpty() ? 0 : mChunks[0].mChannelData.Length();
   }
 
   static Type StaticType() { return AUDIO; }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 };
 
 }
 
 #endif /* MOZILLA_AUDIOSEGMENT_H_ */
--- a/content/media/AudioStream.cpp
+++ b/content/media/AudioStream.cpp
@@ -167,16 +167,32 @@ AudioStream::~AudioStream()
 {
   LOG(("AudioStream: delete %p, state %d", this, mState));
   Shutdown();
   if (mDumpFile) {
     fclose(mDumpFile);
   }
 }
 
+size_t
+AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = aMallocSizeOf(this);
+
+  // Possibly add in the future:
+  // - mTimeStretcher
+  // - mLatencyLog
+  // - mCubebStream
+
+  amount += mInserts.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+
+  return amount;
+}
+
 /*static*/ void AudioStream::InitLibrary()
 {
 #ifdef PR_LOGGING
   gAudioStreamLog = PR_NewLogModule("AudioStream");
 #endif
   PrefChanged(PREF_VOLUME_SCALE, nullptr);
   Preferences::RegisterCallback(PrefChanged, PREF_VOLUME_SCALE);
   PrefChanged(PREF_CUBEB_LATENCY, nullptr);
--- a/content/media/AudioStream.h
+++ b/content/media/AudioStream.h
@@ -164,16 +164,23 @@ public:
       return mCount;
     }
     mStart += (mCount - aSize);
     mCount = aSize;
     mStart %= mCapacity;
     return mCount;
   }
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t amount = 0;
+    amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
 private:
   nsAutoArrayPtr<uint8_t> mBuffer;
   uint32_t mCapacity;
   uint32_t mStart;
   uint32_t mCount;
 };
 
 class AudioInitTask;
@@ -275,16 +282,18 @@ public:
   // This should be called before attempting to use the time stretcher.
   nsresult EnsureTimeStretcherInitialized();
   // Set playback rate as a multiple of the intrinsic playback rate. This is to
   // be called only with aPlaybackRate > 0.0.
   nsresult SetPlaybackRate(double aPlaybackRate);
   // Switch between resampling (if false) and time stretching (if true, default).
   nsresult SetPreservesPitch(bool aPreservesPitch);
 
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
 private:
   friend class AudioInitTask;
 
   // So we can call it asynchronously from AudioInitTask
   nsresult OpenCubeb(cubeb_stream_params &aParams,
                      LatencyRequest aLatencyRequest);
 
   void CheckForStart();
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -1765,16 +1765,18 @@ MediaMemoryTracker::CollectReports(nsIHa
 
   REPORT("explicit/media/decoded/audio", audio,
          "Memory used by decoded audio chunks.");
 
   REPORT("explicit/media/resources", resources,
          "Memory used by media resources including streaming buffers, caches, "
          "etc.");
 
+#undef REPORT
+
   return NS_OK;
 }
 
 MediaDecoderOwner*
 MediaDecoder::GetOwner()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mOwner;
--- a/content/media/MediaSegment.h
+++ b/content/media/MediaSegment.h
@@ -113,16 +113,26 @@ public:
    * Replace contents with disabled data of the same duration
    */
   virtual void ReplaceWithDisabled() = 0;
   /**
    * Remove all contents, setting duration to 0.
    */
   virtual void Clear() = 0;
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return 0;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 protected:
   MediaSegment(Type aType) : mDuration(0), mType(aType)
   {
     MOZ_COUNT_CTOR(MediaSegment);
   }
 
   TrackTicks mDuration; // total of mDurations of all chunks
   Type mType;
@@ -240,16 +250,30 @@ public:
   }
 
 #ifdef MOZILLA_INTERNAL_API
   void GetStartTime(TimeStamp &aTime) {
     aTime = mChunks[0].mTimeStamp;
   }
 #endif
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = mChunks.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < mChunks.Length(); i++) {
+      amount += mChunks[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+    }
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 protected:
   MediaSegmentBase(Type aType) : MediaSegment(aType) {}
 
   /**
    * Appends the contents of aSource to this segment, clearing aSource.
    */
   void AppendFromInternal(MediaSegmentBase<C, Chunk>* aSource)
   {
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -8,18 +8,20 @@
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/unused.h"
 
 #include "AudioSegment.h"
 #include "VideoSegment.h"
 #include "nsContentUtils.h"
 #include "nsIAppShell.h"
 #include "nsIObserver.h"
+#include "nsPrintfCString.h"
 #include "nsServiceManagerUtils.h"
 #include "nsWidgetsCID.h"
+#include "prerror.h"
 #include "prlog.h"
 #include "mozilla/Attributes.h"
 #include "TrackUnionStream.h"
 #include "ImageContainer.h"
 #include "AudioChannelService.h"
 #include "AudioNodeEngine.h"
 #include "AudioNodeStream.h"
 #include "AudioNodeExternalInputStream.h"
@@ -1233,16 +1235,35 @@ MediaStreamGraphImpl::RunThread()
   }
   NS_ASSERTION(!messageQueue.IsEmpty(),
                "Shouldn't have started a graph with empty message queue!");
 
   uint32_t ticksProcessed = 0;
   AutoProfilerUnregisterThread autoUnregister;
 
   for (;;) {
+    // Check if a memory report has been requested.
+    {
+      MonitorAutoLock lock(mMemoryReportMonitor);
+      if (mNeedsMemoryReport) {
+        mNeedsMemoryReport = false;
+
+        for (uint32_t i = 0; i < mStreams.Length(); ++i) {
+          AudioNodeStream* stream = mStreams[i]->AsAudioNodeStream();
+          if (stream) {
+            AudioNodeSizes usage;
+            stream->SizeOfAudioNodesIncludingThis(MallocSizeOf, usage);
+            mAudioStreamSizes.AppendElement(usage);
+          }
+        }
+
+        lock.Notify();
+      }
+    }
+
     // Update mCurrentTime to the min of the playing audio times, or using the
     // wall-clock time change if no audio is playing.
     UpdateCurrentTime();
 
     // Calculate independent action times for each batch of messages (each
     // batch corresponding to an event loop task). This isolates the performance
     // of different scripts to some extent.
     for (uint32_t i = 0; i < messageQueue.Length(); ++i) {
@@ -1514,17 +1535,17 @@ public:
     NS_ASSERTION(mGraph->mDetectedNotRunning,
                  "We should know the graph thread control loop isn't running!");
 
     mGraph->ShutdownThreads();
 
     // mGraph's thread is not running so it's OK to do whatever here
     if (mGraph->IsEmpty()) {
       // mGraph is no longer needed, so delete it.
-      delete mGraph;
+      mGraph->Destroy();
     } else {
       // The graph is not empty.  We must be in a forced shutdown, or a
       // non-realtime graph that has finished processing.  Some later
       // AppendMessage will detect that the manager has been emptied, and
       // delete it.
       NS_ASSERTION(mGraph->mForceShutDown || !mGraph->mRealtime,
                    "Not in forced shutdown?");
       for (uint32_t i = 0; i < mGraph->mStreams.Length(); ++i) {
@@ -1744,17 +1765,17 @@ MediaStreamGraphImpl::AppendMessage(Cont
     // graph has finished processing.
     aMessage->RunDuringShutdown();
     delete aMessage;
     if (IsEmpty() &&
         mLifecycleState >= LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION) {
       if (gGraph == this) {
         gGraph = nullptr;
       }
-      delete this;
+      Destroy();
     }
     return;
   }
 
   mCurrentTaskMessageQueue.AppendElement(aMessage);
   EnsureRunInStableState();
 }
 
@@ -1777,16 +1798,55 @@ MediaStream::MediaStream(DOMMediaStream*
   MOZ_COUNT_CTOR(MediaStream);
   // aWrapper should not already be connected to a MediaStream! It needs
   // to be hooked up to this stream, and since this stream is only just
   // being created now, aWrapper must not be connected to anything.
   NS_ASSERTION(!aWrapper || !aWrapper->GetStream(),
                "Wrapper already has another media stream hooked up to it!");
 }
 
+size_t
+MediaStream::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = 0;
+
+  // Not owned:
+  // - mGraph - Not reported here
+  // - mConsumers - elements
+  // Future:
+  // - mWrapper
+  // - mVideoOutputs - elements
+  // - mLastPlayedVideoFrame
+  // - mListeners - elements
+  // - mAudioOutputStreams - elements
+
+  amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mAudioOutputs.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mVideoOutputs.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mExplicitBlockerCount.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mListeners.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mMainThreadListeners.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mDisabledTrackIDs.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mBlocked.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mGraphUpdateIndices.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mConsumers.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mAudioOutputStreams.SizeOfExcludingThis(aMallocSizeOf);
+  for (size_t i = 0; i < mAudioOutputStreams.Length(); i++) {
+    amount += mAudioOutputStreams[i].SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  return amount;
+}
+
+size_t
+MediaStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 void
 MediaStream::Init()
 {
   MediaStreamGraphImpl* graph = GraphImpl();
   mBlocked.SetAtAndAfter(graph->mCurrentTime, true);
   mExplicitBlockerCount.SetAtAndAfter(graph->mCurrentTime, true);
   mExplicitBlockerCount.SetAtAndAfter(graph->mStateComputedTime, false);
 }
@@ -2589,24 +2649,40 @@ MediaStreamGraphImpl::MediaStreamGraphIm
   , mPostedRunInStableStateEvent(false)
   , mDetectedNotRunning(false)
   , mPostedRunInStableState(false)
   , mRealtime(aRealtime)
   , mNonRealtimeProcessing(false)
   , mStreamOrderDirty(false)
   , mLatencyLog(AsyncLatencyLogger::Get())
   , mMixer(nullptr)
+  , mMemoryReportMonitor("MSGIMemory")
+  , mSelfRef(MOZ_THIS_IN_INITIALIZER_LIST())
+  , mAudioStreamSizes()
+  , mNeedsMemoryReport(false)
 {
 #ifdef PR_LOGGING
   if (!gMediaStreamGraphLog) {
     gMediaStreamGraphLog = PR_NewLogModule("MediaStreamGraph");
   }
 #endif
 
   mCurrentTimeStamp = mInitialTimeStamp = mLastMainThreadUpdate = TimeStamp::Now();
+
+  RegisterWeakMemoryReporter(this);
+}
+
+void
+MediaStreamGraphImpl::Destroy()
+{
+  // First unregister from memory reporting.
+  UnregisterWeakMemoryReporter(this);
+
+  // Clear the self reference which will destroy this instance.
+  mSelfRef = nullptr;
 }
 
 NS_IMPL_ISUPPORTS1(MediaStreamGraphShutdownObserver, nsIObserver)
 
 static bool gShutdownObserverRegistered = false;
 
 NS_IMETHODIMP
 MediaStreamGraphShutdownObserver::Observe(nsISupports *aSubject,
@@ -2630,30 +2706,32 @@ MediaStreamGraph::GetInstance()
 
   if (!gGraph) {
     if (!gShutdownObserverRegistered) {
       gShutdownObserverRegistered = true;
       nsContentUtils::RegisterShutdownObserver(new MediaStreamGraphShutdownObserver());
     }
 
     gGraph = new MediaStreamGraphImpl(true);
+
     STREAM_LOG(PR_LOG_DEBUG, ("Starting up MediaStreamGraph %p", gGraph));
 
     AudioStream::InitPreferredSampleRate();
   }
 
   return gGraph;
 }
 
 MediaStreamGraph*
 MediaStreamGraph::CreateNonRealtimeInstance()
 {
   NS_ASSERTION(NS_IsMainThread(), "Main thread only");
 
   MediaStreamGraphImpl* graph = new MediaStreamGraphImpl(false);
+
   return graph;
 }
 
 void
 MediaStreamGraph::DestroyNonRealtimeInstance(MediaStreamGraph* aGraph)
 {
   NS_ASSERTION(NS_IsMainThread(), "Main thread only");
   MOZ_ASSERT(aGraph->IsNonRealtime(), "Should not destroy the global graph here");
@@ -2664,16 +2742,86 @@ MediaStreamGraph::DestroyNonRealtimeInst
 
   if (!graph->mNonRealtimeProcessing) {
     // Start the graph, but don't produce anything
     graph->StartNonRealtimeProcessing(1, 0);
   }
   graph->ForceShutDown();
 }
 
+NS_IMPL_ISUPPORTS1(MediaStreamGraphImpl, nsIMemoryReporter)
+
+struct ArrayClearer
+{
+  ArrayClearer(nsTArray<AudioNodeSizes>& aArray) : mArray(aArray) {}
+  ~ArrayClearer() { mArray.Clear(); }
+  nsTArray<AudioNodeSizes>& mArray;
+};
+
+NS_IMETHODIMP
+MediaStreamGraphImpl::CollectReports(nsIHandleReportCallback* aHandleReport,
+                                     nsISupports* aData)
+{
+  // Clears out the report array after we're done with it.
+  ArrayClearer reportCleanup(mAudioStreamSizes);
+
+  {
+    MonitorAutoLock memoryReportLock(mMemoryReportMonitor);
+    mNeedsMemoryReport = true;
+
+    {
+      // Wake up the MSG thread.
+      MonitorAutoLock monitorLock(mMonitor);
+      EnsureImmediateWakeUpLocked(monitorLock);
+    }
+
+    // Wait for the report to complete.
+    nsresult rv;
+    while ((rv = memoryReportLock.Wait()) != NS_OK) {
+      if (PR_GetError() != PR_PENDING_INTERRUPT_ERROR) {
+        return rv;
+      }
+    }
+  }
+
+#define REPORT(_path, _amount, _desc)                                       \
+  do {                                                                      \
+    nsresult rv;                                                            \
+    rv = aHandleReport->Callback(EmptyCString(), _path,                     \
+                                 KIND_HEAP, UNITS_BYTES, _amount,           \
+                                 NS_LITERAL_CSTRING(_desc), aData);         \
+    NS_ENSURE_SUCCESS(rv, rv);                                              \
+  } while (0)
+
+  for (size_t i = 0; i < mAudioStreamSizes.Length(); i++) {
+    const AudioNodeSizes& usage = mAudioStreamSizes[i];
+    const char* const nodeType =  usage.mNodeType.get();
+
+    nsPrintfCString domNodePath("explicit/webaudio/audio-node/%s/dom-nodes",
+                                  nodeType);
+    REPORT(domNodePath, usage.mDomNode,
+           "Memory used by AudioNode DOM objects (Web Audio).");
+
+    nsPrintfCString enginePath("explicit/webaudio/audio-node/%s/engine-objects",
+                                nodeType);
+    REPORT(enginePath, usage.mEngine,
+           "Memory used by AudioNode engine objects (Web Audio).");
+
+    nsPrintfCString streamPath("explicit/webaudio/audio-node/%s/stream-objects",
+                                nodeType);
+    REPORT(streamPath, usage.mStream,
+           "Memory used by AudioNode stream objects (Web Audio).");
+
+  }
+
+#undef REPORT
+
+  return NS_OK;
+}
+
 SourceMediaStream*
 MediaStreamGraph::CreateSourceStream(DOMMediaStream* aWrapper)
 {
   SourceMediaStream* stream = new SourceMediaStream(aWrapper);
   NS_ADDREF(stream);
   MediaStreamGraphImpl* graph = static_cast<MediaStreamGraphImpl*>(this);
   stream->SetGraphImpl(graph);
   graph->AppendMessage(new CreateMessage(stream));
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -221,16 +221,27 @@ public:
  * The listener is allowed to synchronously remove itself from the stream, but
  * not add or remove any other listeners.
  */
 class MainThreadMediaStreamListener {
 public:
   virtual void NotifyMainThreadStateChanged() = 0;
 };
 
+/**
+ * Helper struct used to keep track of memory usage by AudioNodes.
+ */
+struct AudioNodeSizes
+{
+  size_t mDomNode;
+  size_t mStream;
+  size_t mEngine;
+  nsCString mNodeType;
+};
+
 class MediaStreamGraphImpl;
 class SourceMediaStream;
 class ProcessedMediaStream;
 class MediaInputPort;
 class AudioNodeEngine;
 class AudioNodeExternalInputStream;
 class AudioNodeStream;
 struct AudioChunk;
@@ -505,16 +516,19 @@ public:
   }
 
   // Return true if the main thread needs to observe updates from this stream.
   virtual bool MainThreadNeedsUpdates() const
   {
     return true;
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
 protected:
   virtual void AdvanceTimeVaryingValuesToCurrentTime(GraphTime aCurrentTime, GraphTime aBlockedTime)
   {
     mBufferStartTime += aBlockedTime;
     mGraphUpdateIndices.InsertTimeAtStart(aBlockedTime);
     mGraphUpdateIndices.AdvanceCurrentTime(aCurrentTime);
     mExplicitBlockerCount.AdvanceCurrentTime(aCurrentTime);
 
@@ -572,16 +586,23 @@ protected:
     GraphTime mAudioPlaybackStartTime;
     // Amount of time that we've wanted to play silence because of the stream
     // blocking.
     MediaTime mBlockedAudioTime;
     // Last tick written to the audio output.
     TrackTicks mLastTickWritten;
     RefPtr<AudioStream> mStream;
     TrackID mTrackID;
+
+    size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+    {
+      size_t amount = 0;
+      amount += mStream->SizeOfIncludingThis(aMallocSizeOf);
+      return amount;
+    }
   };
   nsTArray<AudioOutputStream> mAudioOutputStreams;
 
   /**
    * When true, this means the stream will be finished once all
    * buffered data has been consumed.
    */
   bool mFinished;
@@ -915,16 +936,32 @@ public:
    */
   MediaStreamGraphImpl* GraphImpl();
   MediaStreamGraph* Graph();
   /**
    * Sets the graph that owns this stream.  Should only be called once.
    */
   void SetGraphImpl(MediaStreamGraphImpl* aGraph);
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t amount = 0;
+
+    // Not owned:
+    // - mSource
+    // - mDest
+    // - mGraph
+    return amount;
+  }
+
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   friend class MediaStreamGraphImpl;
   friend class MediaStream;
   friend class ProcessedMediaStream;
   // Never modified after Init()
   MediaStream* mSource;
   ProcessedMediaStream* mDest;
   uint32_t mFlags;
@@ -1011,16 +1048,29 @@ public:
 
   /**
    * Forward SetTrackEnabled() to the input MediaStream(s) and translate the ID
    */
   virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) {};
 
   bool InCycle() const { return mInCycle; }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = MediaStream::SizeOfExcludingThis(aMallocSizeOf);
+    // Not owned:
+    // - mInputs elements
+    amount += mInputs.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 
 protected:
   // This state is all accessed only on the media graph thread.
 
   // The list of all inputs that are currently enabled or waiting to be enabled.
   nsTArray<MediaInputPort*> mInputs;
   bool mAutofinish;
   // True if and only if this stream is in a cycle.
--- a/content/media/MediaStreamGraphImpl.h
+++ b/content/media/MediaStreamGraphImpl.h
@@ -5,16 +5,17 @@
 
 #ifndef MOZILLA_MEDIASTREAMGRAPHIMPL_H_
 #define MOZILLA_MEDIASTREAMGRAPHIMPL_H_
 
 #include "MediaStreamGraph.h"
 
 #include "mozilla/Monitor.h"
 #include "mozilla/TimeStamp.h"
+#include "nsIMemoryReporter.h"
 #include "nsIThread.h"
 #include "nsIRunnable.h"
 #include "Latency.h"
 #include "mozilla/WeakPtr.h"
 
 namespace mozilla {
 
 template <typename T>
@@ -101,27 +102,36 @@ protected:
 /**
  * The implementation of a media stream graph. This class is private to this
  * file. It's not in the anonymous namespace because MediaStream needs to
  * be able to friend it.
  *
  * Currently we have one global instance per process, and one per each
  * OfflineAudioContext object.
  */
-class MediaStreamGraphImpl : public MediaStreamGraph {
+class MediaStreamGraphImpl : public MediaStreamGraph,
+                             public nsIMemoryReporter {
 public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIMEMORYREPORTER
+
   /**
    * Set aRealtime to true in order to create a MediaStreamGraph which provides
    * support for real-time audio and video.  Set it to false in order to create
    * a non-realtime instance which just churns through its inputs and produces
    * output.  Those objects currently only support audio, and are used to
    * implement OfflineAudioContext.  They do not support MediaStream inputs.
    */
   explicit MediaStreamGraphImpl(bool aRealtime);
-  virtual ~MediaStreamGraphImpl();
+
+  /**
+   * Unregisters memory reporting and deletes this instance. This should be
+   * called instead of calling the destructor directly.
+   */
+  void Destroy();
 
   // Main thread only.
   /**
    * This runs every time we need to sync state from the media graph thread
    * to the main thread while the main thread is not in the middle
    * of a script. It runs during a "stable state" (per HTML5) or during
    * an event posted to the main thread.
    */
@@ -573,13 +583,39 @@ public:
   /**
    * Hold a ref to the Latency logger
    */
   nsRefPtr<AsyncLatencyLogger> mLatencyLog;
   /**
    * If this is not null, all the audio output for the MSG will be mixed down.
    */
   nsAutoPtr<AudioMixer> mMixer;
+
+private:
+  virtual ~MediaStreamGraphImpl();
+
+  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+  /**
+   * Used to signal that a memory report has been requested.
+   */
+  Monitor mMemoryReportMonitor;
+  /**
+   * This class uses manual memory management, and all pointers to it are raw
+   * pointers. However, in order for it to implement nsIMemoryReporter, it needs
+   * to implement nsISupports and so be ref-counted. So it maintains a single
+   * nsRefPtr to itself, giving it a ref-count of 1 during its entire lifetime,
+   * and Destroy() nulls this self-reference in order to trigger self-deletion.
+   */
+  nsRefPtr<MediaStreamGraphImpl> mSelfRef;
+  /**
+   * Used to pass memory report information across threads.
+   */
+  nsTArray<AudioNodeSizes> mAudioStreamSizes;
+  /**
+   * Indicates that the MSG thread should gather data for a memory report.
+   */
+  bool mNeedsMemoryReport;
 };
 
 }
 
 #endif /* MEDIASTREAMGRAPHIMPL_H_ */
--- a/content/media/SharedBuffer.h
+++ b/content/media/SharedBuffer.h
@@ -16,16 +16,27 @@ namespace mozilla {
 /**
  * Base class for objects with a thread-safe refcount and a virtual
  * destructor.
  */
 class ThreadSharedObject {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadSharedObject)
 
+  bool IsShared() { return mRefCnt.get() > 1; }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return 0;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~ThreadSharedObject() {}
 };
 
 /**
  * Heap-allocated chunk of arbitrary data with threadsafe refcounting.
  * Typically you would allocate one of these, fill it in, and then treat it as
@@ -47,18 +58,19 @@ public:
     }
     void* m = moz_xmalloc(size.value());
     nsRefPtr<SharedBuffer> p = new (m) SharedBuffer();
     NS_ASSERTION((reinterpret_cast<char*>(p.get() + 1) - reinterpret_cast<char*>(p.get())) % 4 == 0,
                  "SharedBuffers should be at least 4-byte aligned");
     return p.forget();
   }
 
-  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
-    return aMallocSizeOf(this);
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
 private:
   SharedBuffer() {}
 };
 
 }
 
--- a/content/media/StreamBuffer.h
+++ b/content/media/StreamBuffer.h
@@ -158,16 +158,25 @@ public:
     {
       return mSegment.forget();
     }
     void ForgetUpTo(TrackTicks aTime)
     {
       mSegment->ForgetUpTo(aTime);
     }
 
+    size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+    {
+      size_t amount = aMallocSizeOf(this);
+      if (mSegment) {
+        amount += mSegment->SizeOfIncludingThis(aMallocSizeOf);
+      }
+      return amount;
+    }
+
   protected:
     friend class StreamBuffer;
 
     // Start offset is in ticks at rate mRate
     TrackTicks mStart;
     // The segment data starts at the start of the owning StreamBuffer, i.e.,
     // there's mStart silence/no video at the beginning.
     nsAutoPtr<MediaSegment> mSegment;
@@ -193,16 +202,26 @@ public:
   {
     MOZ_COUNT_CTOR(StreamBuffer);
   }
   ~StreamBuffer()
   {
     MOZ_COUNT_DTOR(StreamBuffer);
   }
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t amount = 0;
+    amount += mTracks.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < mTracks.Length(); i++) {
+      amount += mTracks[i]->SizeOfIncludingThis(aMallocSizeOf);
+    }
+    return amount;
+  }
+
   /**
    * Takes ownership of aSegment. Don't do this while iterating, or while
    * holding a Track reference.
    * aSegment must have aStart worth of null data.
    */
   Track& AddTrack(TrackID aID, TrackRate aRate, TrackTicks aStart, MediaSegment* aSegment)
   {
     NS_ASSERTION(TimeToTicksRoundDown(aRate, mTracksKnownTime) <= aStart,
--- a/content/media/TimeVarying.h
+++ b/content/media/TimeVarying.h
@@ -210,16 +210,21 @@ public:
     NS_ASSERTION(&aOther != this, "Can't self-append");
     SetAtAndAfter(aTimeOffset, aOther.mCurrent);
     for (uint32_t i = 0; i < aOther.mChanges.Length(); ++i) {
       const Entry& e = aOther.mChanges[i];
       SetAtAndAfter(aTimeOffset + e.mTime, e.mValue);
     }
   }
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return mChanges.SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   struct Entry {
     Entry(Time aTime, const T& aValue) : mTime(aTime), mValue(aValue) {}
 
     // The time at which the value changes to mValue
     Time mTime;
     T mValue;
   };
--- a/content/media/VideoSegment.h
+++ b/content/media/VideoSegment.h
@@ -71,16 +71,23 @@ struct VideoChunk {
   void SetNull(TrackTicks aDuration)
   {
     mDuration = aDuration;
     mFrame.SetNull();
     mTimeStamp = TimeStamp();
   }
   void SetForceBlack(bool aForceBlack) { mFrame.SetForceBlack(aForceBlack); }
 
+  size_t SizeOfExcludingThisIfUnshared(MallocSizeOf aMallocSizeOf) const
+  {
+    // Future:
+    // - mFrame
+    return 0;
+  }
+
   TrackTicks mDuration;
   VideoFrame mFrame;
   mozilla::TimeStamp mTimeStamp;
 };
 
 class VideoSegment : public MediaSegmentBase<VideoSegment, VideoChunk> {
 public:
   typedef mozilla::layers::Image Image;
@@ -116,13 +123,18 @@ public:
          !i.IsEnded(); i.Next()) {
       VideoChunk& chunk = *i;
       chunk.SetForceBlack(true);
     }
   }
 
   // Segment-generic methods not in MediaSegmentBase
   static Type StaticType() { return VIDEO; }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 };
 
 }
 
 #endif /* MOZILLA_VIDEOSEGMENT_H_ */
--- a/content/media/webaudio/AnalyserNode.cpp
+++ b/content/media/webaudio/AnalyserNode.cpp
@@ -67,16 +67,21 @@ public:
     MutexAutoLock lock(NodeMutex());
 
     if (Node() &&
         aInput.mChannelData.Length() > 0) {
       nsRefPtr<TransferBuffer> transfer = new TransferBuffer(aStream, aInput);
       NS_DispatchToMainThread(transfer);
     }
   }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 };
 
 AnalyserNode::AnalyserNode(AudioContext* aContext)
   : AudioNode(aContext,
               1,
               ChannelCountMode::Explicit,
               ChannelInterpretation::Speakers)
   , mAnalysisBlock(2048)
@@ -85,16 +90,32 @@ AnalyserNode::AnalyserNode(AudioContext*
   , mSmoothingTimeConstant(.8)
   , mWriteIndex(0)
 {
   mStream = aContext->Graph()->CreateAudioNodeStream(new AnalyserNodeEngine(this),
                                                      MediaStreamGraph::INTERNAL_STREAM);
   AllocateBuffer();
 }
 
+size_t
+AnalyserNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mAnalysisBlock.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mOutputBuffer.SizeOfExcludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+AnalyserNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 AnalyserNode::WrapObject(JSContext* aCx)
 {
   return AnalyserNodeBinding::Wrap(aCx, this);
 }
 
 void
 AnalyserNode::SetFftSize(uint32_t aValue, ErrorResult& aRv)
--- a/content/media/webaudio/AnalyserNode.h
+++ b/content/media/webaudio/AnalyserNode.h
@@ -48,16 +48,24 @@ public:
   }
   void SetMaxDecibels(double aValue, ErrorResult& aRv);
   double SmoothingTimeConstant() const
   {
     return mSmoothingTimeConstant;
   }
   void SetSmoothingTimeConstant(double aValue, ErrorResult& aRv);
 
+  virtual const char* NodeType() const
+  {
+    return "AnalyserNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   friend class AnalyserNodeEngine;
   void AppendChunk(const AudioChunk& aChunk);
   bool AllocateBuffer();
   bool FFTAnalysis();
   void ApplyBlackmanWindow(float* aBuffer, uint32_t aSize);
 
 private:
--- a/content/media/webaudio/AudioBufferSourceNode.cpp
+++ b/content/media/webaudio/AudioBufferSourceNode.cpp
@@ -477,16 +477,41 @@ public:
     // We've finished if we've gone past mStop, or if we're past mDuration when
     // looping is disabled.
     if (streamPosition >= mStop ||
         (!mLoop && mBufferPosition >= mBufferEnd && !mRemainingResamplerTail)) {
       *aFinished = true;
     }
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    // Not owned:
+    // - mBuffer - shared w/ AudioNode
+    // - mPlaybackRateTimeline - shared w/ AudioNode
+
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+
+    // NB: We need to modify speex if we want the full memory picture, internal
+    //     fields that need measuring noted below.
+    // - mResampler->mem
+    // - mResampler->sinc_table
+    // - mResampler->last_sample
+    // - mResampler->magic_samples
+    // - mResampler->samp_frac_num
+    amount += aMallocSizeOf(mResampler);
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
   double mStart; // including the fractional position between ticks
   // Low pass filter effects from the resampler mean that samples before the
   // start time are influenced by resampling the buffer.  mBeginProcessing
   // includes the extent of this filter.  The special value of -TRACK_TICKS_MAX
   // indicates that the resampler has begun processing.
   TrackTicks mBeginProcessing;
   TrackTicks mStop;
   nsRefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
@@ -529,16 +554,34 @@ AudioBufferSourceNode::AudioBufferSource
 
 AudioBufferSourceNode::~AudioBufferSourceNode()
 {
   if (Context()) {
     Context()->UnregisterAudioBufferSourceNode(this);
   }
 }
 
+size_t
+AudioBufferSourceNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  if (mBuffer) {
+    amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  amount += mPlaybackRate->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+AudioBufferSourceNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 AudioBufferSourceNode::WrapObject(JSContext* aCx)
 {
   return AudioBufferSourceNodeBinding::Wrap(aCx, this);
 }
 
 void
 AudioBufferSourceNode::Start(double aWhen, double aOffset,
--- a/content/media/webaudio/AudioBufferSourceNode.h
+++ b/content/media/webaudio/AudioBufferSourceNode.h
@@ -103,16 +103,24 @@ public:
     SendLoopParametersToStream();
   }
   void SendDopplerShiftToStream(double aDopplerShift);
 
   IMPL_EVENT_HANDLER(ended)
 
   virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
 
+  virtual const char* NodeType() const
+  {
+    return "AudioBufferSourceNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   friend class AudioBufferSourceNodeEngine;
   // START is sent during Start().
   // STOP is sent during Stop().
   // BUFFERSTART and BUFFEREND are sent when SetBuffer() and Start() have
   // been called (along with sending the buffer).
   enum EngineParameters {
     SAMPLE_RATE,
--- a/content/media/webaudio/AudioDestinationNode.cpp
+++ b/content/media/webaudio/AudioDestinationNode.cpp
@@ -142,16 +142,28 @@ public:
     }
 
     nsRefPtr<OfflineAudioCompletionEvent> event =
         new OfflineAudioCompletionEvent(context, nullptr, nullptr);
     event->InitEvent(renderedBuffer);
     context->DispatchTrustedEvent(event);
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    amount += mInputChannels.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   // The input to the destination node is recorded in the mInputChannels buffer.
   // When this buffer fills up with mLength frames, the buffered input is sent
   // to the main thread in order to dispatch OfflineAudioCompletionEvent.
   InputChannels mInputChannels;
   // An index representing the next offset in mInputChannels to be written to.
   uint32_t mWriteIndex;
   // How many frames the OfflineAudioContext intends to produce.
@@ -183,16 +195,21 @@ public:
       mVolume = aParam;
     }
   }
 
   enum Parameters {
     VOLUME,
   };
 
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   float mVolume;
 };
 
 static bool UseAudioChannelService()
 {
   return Preferences::GetBool("media.useAudioChannelService");
 }
@@ -251,16 +268,31 @@ AudioDestinationNode::AudioDestinationNo
                                      /* useCapture = */ true,
                                      /* wantsUntrusted = */ false);
     }
 
     CreateAudioChannelAgent();
   }
 }
 
+size_t
+AudioDestinationNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  // Might be useful in the future:
+  // - mAudioChannelAgent
+  return amount;
+}
+
+size_t
+AudioDestinationNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 void
 AudioDestinationNode::DestroyMediaStream()
 {
   if (mAudioChannelAgent && !Context()->IsOffline()) {
     mAudioChannelAgent->StopPlaying();
     mAudioChannelAgent = nullptr;
 
     nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
--- a/content/media/webaudio/AudioDestinationNode.h
+++ b/content/media/webaudio/AudioDestinationNode.h
@@ -69,16 +69,24 @@ public:
 
   // An amount that should be added to the MediaStream's current time to
   // get the AudioContext.currentTime.
   double ExtraCurrentTime();
 
   // When aIsOnlyNode is true, this is the only node for the AudioContext.
   void SetIsOnlyNodeForContext(bool aIsOnlyNode);
 
+  virtual const char* NodeType() const
+  {
+    return "AudioDestinationNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   bool CheckAudioChannelPermissions(AudioChannel aValue);
   void CreateAudioChannelAgent();
 
   void SetCanPlay(bool aCanPlay);
 
   void NotifyStableState();
   void ScheduleStableStateNotification();
--- a/content/media/webaudio/AudioNode.cpp
+++ b/content/media/webaudio/AudioNode.cpp
@@ -72,16 +72,47 @@ AudioNode::~AudioNode()
   MOZ_ASSERT(mInputNodes.IsEmpty());
   MOZ_ASSERT(mOutputNodes.IsEmpty());
   MOZ_ASSERT(mOutputParams.IsEmpty());
   if (mContext) {
     mContext->UpdateNodeCount(-1);
   }
 }
 
+size_t
+AudioNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  // Not owned:
+  // - mContext
+  // - mStream
+  size_t amount = 0;
+
+  amount += mInputNodes.SizeOfExcludingThis(aMallocSizeOf);
+  for (size_t i = 0; i < mInputNodes.Length(); i++) {
+    amount += mInputNodes[i].SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  // Just measure the array. The entire audio node graph is measured via the
+  // MediaStreamGraph's streams, so we don't want to double-count the elements.
+  amount += mOutputNodes.SizeOfExcludingThis(aMallocSizeOf);
+
+  amount += mOutputParams.SizeOfExcludingThis(aMallocSizeOf);
+  for (size_t i = 0; i < mOutputParams.Length(); i++) {
+    amount += mOutputParams[i]->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  return amount;
+}
+
+size_t
+AudioNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 template <class InputNode>
 static uint32_t
 FindIndexOfNode(const nsTArray<InputNode>& aInputNodes, const AudioNode* aNode)
 {
   for (uint32_t i = 0; i < aInputNodes.Length(); ++i) {
     if (aInputNodes[i].mInputNode == aNode) {
       return i;
     }
--- a/content/media/webaudio/AudioNode.h
+++ b/content/media/webaudio/AudioNode.h
@@ -10,16 +10,17 @@
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/AudioNodeBinding.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 #include "AudioContext.h"
 #include "MediaStreamGraph.h"
 #include "WebAudioUtils.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace mozilla {
 
 namespace dom {
 
 class AudioContext;
 class AudioBufferSourceNode;
 class AudioParam;
@@ -165,16 +166,26 @@ public:
   struct InputNode {
     ~InputNode()
     {
       if (mStreamPort) {
         mStreamPort->Destroy();
       }
     }
 
+    size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+    {
+      size_t amount = 0;
+      if (mStreamPort) {
+        amount += mStreamPort->SizeOfIncludingThis(aMallocSizeOf);
+      }
+
+      return amount;
+    }
+
     // Weak reference.
     AudioNode* mInputNode;
     nsRefPtr<MediaInputPort> mStreamPort;
     // The index of the input port this node feeds into.
     // This is not used for connections to AudioParams.
     uint32_t mInputPort;
     // The index of the output port this node comes out of.
     uint32_t mOutputPort;
@@ -203,16 +214,21 @@ public:
   void MarkActive() { Context()->RegisterActiveNode(this); }
   // Active nodes call MarkInactive() when they have finished producing sound
   // for the foreseeable future.
   // Do not call MarkInactive from a node destructor.  If the destructor is
   // called, then the node is already inactive.
   // MarkInactive() may delete |this|.
   void MarkInactive() { Context()->UnregisterActiveNode(this); }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+  virtual const char* NodeType() const = 0;
+
 private:
   friend class AudioBufferSourceNode;
   // This could possibly delete 'this'.
   void DisconnectFromGraph();
 
 protected:
   static void Callback(AudioNode* aNode) { /* not implemented */ }
 
--- a/content/media/webaudio/AudioParam.h
+++ b/content/media/webaudio/AudioParam.h
@@ -148,16 +148,37 @@ public:
     return mInputNodes.AppendElement();
   }
 
   void DisconnectFromGraphAndDestroyStream();
 
   // May create the stream if it doesn't exist
   MediaStream* Stream();
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioParamTimeline::SizeOfExcludingThis(aMallocSizeOf);
+    // Not owned:
+    // - mNode
+
+    // Just count the array, actual nodes are counted in mNode.
+    amount += mInputNodes.SizeOfExcludingThis(aMallocSizeOf);
+
+    if (mNodeStreamPort) {
+      amount += mNodeStreamPort->SizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 protected:
   nsCycleCollectingAutoRefCnt mRefCnt;
   NS_DECL_OWNINGTHREAD
 
 private:
   nsRefPtr<AudioNode> mNode;
   // For every InputNode, there is a corresponding entry in mOutputParams of the
   // InputNode's mInputNode.
--- a/content/media/webaudio/AudioParamTimeline.h
+++ b/content/media/webaudio/AudioParamTimeline.h
@@ -47,16 +47,27 @@ public:
   // Get the value of the AudioParam at time aTime + aCounter.
   // aCounter here is an offset to aTime if we try to get the value in ticks,
   // otherwise it should always be zero.  aCounter is meant to be used when
   // getting the value of an a-rate AudioParam for each tick inside an
   // AudioNodeEngine implementation.
   template<class TimeType>
   float GetValueAtTime(TimeType aTime, size_t aCounter = 0);
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return mStream ? mStream->SizeOfIncludingThis(aMallocSizeOf) : 0;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+
 private:
   float AudioNodeInputValue(size_t aCounter) const;
 
 protected:
   // This is created lazily when needed.
   nsRefPtr<MediaStream> mStream;
 };
 
--- a/content/media/webaudio/BiquadFilterNode.cpp
+++ b/content/media/webaudio/BiquadFilterNode.cpp
@@ -206,16 +206,32 @@ public:
       SetParamsOnBiquad(mBiquads[i], aStream->SampleRate(), mType, freq, q, gain, detune);
 
       mBiquads[i].process(input,
                           static_cast<float*>(const_cast<void*>(aOutput->mChannelData[i])),
                           aInput.GetDuration());
     }
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    // Not owned:
+    // - mSource - probably not owned
+    // - mDestination - probably not owned
+    // - AudioParamTimelines - counted in the AudioNode
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    amount += mBiquads.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   AudioNodeStream* mSource;
   AudioNodeStream* mDestination;
   BiquadFilterType mType;
   AudioParamTimeline mFrequency;
   AudioParamTimeline mDetune;
   AudioParamTimeline mQ;
   AudioParamTimeline mGain;
@@ -237,16 +253,47 @@ BiquadFilterNode::BiquadFilterNode(Audio
   , mGain(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(),
                          SendGainToStream, 0.f))
 {
   BiquadFilterNodeEngine* engine = new BiquadFilterNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
 }
 
+
+size_t
+BiquadFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+
+  if (mFrequency) {
+    amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  if (mDetune) {
+    amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  if (mQ) {
+    amount += mQ->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  if (mGain) {
+    amount += mGain->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  return amount;
+}
+
+size_t
+BiquadFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 BiquadFilterNode::WrapObject(JSContext* aCx)
 {
   return BiquadFilterNodeBinding::Wrap(aCx, this);
 }
 
 void
 BiquadFilterNode::SetType(BiquadFilterType aType)
--- a/content/media/webaudio/BiquadFilterNode.h
+++ b/content/media/webaudio/BiquadFilterNode.h
@@ -51,16 +51,24 @@ public:
   {
     return mGain;
   }
 
   void GetFrequencyResponse(const Float32Array& aFrequencyHz,
                             const Float32Array& aMagResponse,
                             const Float32Array& aPhaseResponse);
 
+  virtual const char* NodeType() const
+  {
+    return "BiquadFilterNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   static void SendFrequencyToStream(AudioNode* aNode);
   static void SendDetuneToStream(AudioNode* aNode);
   static void SendQToStream(AudioNode* aNode);
   static void SendGainToStream(AudioNode* aNode);
 
 private:
   BiquadFilterType mType;
--- a/content/media/webaudio/ChannelMergerNode.cpp
+++ b/content/media/webaudio/ChannelMergerNode.cpp
@@ -48,16 +48,21 @@ public:
         AudioBlockCopyChannelWithScale(
             static_cast<const float*>(aInput[i].mChannelData[j]),
             aInput[i].mVolume,
             static_cast<float*>(const_cast<void*>(aOutput[0].mChannelData[channelIndex])));
         ++channelIndex;
       }
     }
   }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 };
 
 ChannelMergerNode::ChannelMergerNode(AudioContext* aContext,
                                      uint16_t aInputCount)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
--- a/content/media/webaudio/ChannelMergerNode.h
+++ b/content/media/webaudio/ChannelMergerNode.h
@@ -21,16 +21,26 @@ public:
                     uint16_t aInputCount);
 
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   virtual uint16_t NumberOfInputs() const MOZ_OVERRIDE { return mInputCount; }
 
+  virtual const char* NodeType() const
+  {
+    return "ChannelMergerNode";
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   const uint16_t mInputCount;
 };
 
 }
 }
 
 #endif
--- a/content/media/webaudio/ChannelSplitterNode.cpp
+++ b/content/media/webaudio/ChannelSplitterNode.cpp
@@ -40,16 +40,21 @@ public:
             aInput[0].mVolume,
             static_cast<float*>(const_cast<void*>(aOutput[i].mChannelData[0])));
       } else {
         // Pad with silent channels if needed
         aOutput[i].SetNull(WEBAUDIO_BLOCK_SIZE);
       }
     }
   }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 };
 
 ChannelSplitterNode::ChannelSplitterNode(AudioContext* aContext,
                                          uint16_t aOutputCount)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
--- a/content/media/webaudio/ChannelSplitterNode.h
+++ b/content/media/webaudio/ChannelSplitterNode.h
@@ -21,16 +21,26 @@ public:
                       uint16_t aOutputCount);
 
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   virtual uint16_t NumberOfOutputs() const MOZ_OVERRIDE { return mOutputCount; }
 
+  virtual const char* NodeType() const
+  {
+    return "ChannelSplitterNode";
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   const uint16_t mOutputCount;
 };
 
 }
 }
 
 #endif
--- a/content/media/webaudio/ConvolverNode.cpp
+++ b/content/media/webaudio/ConvolverNode.cpp
@@ -149,16 +149,35 @@ public:
       mLeftOverData = mBufferLength;
       MOZ_ASSERT(mLeftOverData > 0);
     }
     AllocateAudioBlock(2, aOutput);
 
     mReverb->process(&input, aOutput, WEBAUDIO_BLOCK_SIZE);
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    if (mBuffer && !mBuffer->IsShared()) {
+      amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    if (mReverb) {
+      amount += mReverb->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   nsRefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
   nsAutoPtr<WebCore::Reverb> mReverb;
   int32_t mBufferLength;
   int32_t mLeftOverData;
   float mSampleRate;
   bool mUseBackgroundThreads;
   bool mNormalize;
@@ -170,16 +189,34 @@ ConvolverNode::ConvolverNode(AudioContex
               ChannelCountMode::Clamped_max,
               ChannelInterpretation::Speakers)
   , mNormalize(true)
 {
   ConvolverNodeEngine* engine = new ConvolverNodeEngine(this, mNormalize);
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
 }
 
+size_t
+ConvolverNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  if (mBuffer) {
+    // NB: mBuffer might be shared with the associated engine, by convention
+    //     the AudioNode will report.
+    amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+  }
+  return amount;
+}
+
+size_t
+ConvolverNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 ConvolverNode::WrapObject(JSContext* aCx)
 {
   return ConvolverNodeBinding::Wrap(aCx, this);
 }
 
 void
 ConvolverNode::SetBuffer(JSContext* aCx, AudioBuffer* aBuffer, ErrorResult& aRv)
--- a/content/media/webaudio/ConvolverNode.h
+++ b/content/media/webaudio/ConvolverNode.h
@@ -49,16 +49,24 @@ public:
   {
     if (aMode == ChannelCountMode::Max) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
     AudioNode::SetChannelCountModeValue(aMode, aRv);
   }
 
+  virtual const char* NodeType() const
+  {
+    return "ConvolverNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   nsRefPtr<AudioBuffer> mBuffer;
   bool mNormalize;
 };
 
 
 } //end namespace dom
 } //end namespace mozilla
--- a/content/media/webaudio/DelayBuffer.cpp
+++ b/content/media/webaudio/DelayBuffer.cpp
@@ -7,16 +7,29 @@
 #include "DelayBuffer.h"
 
 #include "mozilla/PodOperations.h"
 #include "AudioChannelFormat.h"
 #include "AudioNodeEngine.h"
 
 namespace mozilla {
 
+size_t
+DelayBuffer::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = 0;
+  amount += mChunks.SizeOfExcludingThis(aMallocSizeOf);
+  for (size_t i = 0; i < mChunks.Length(); i++) {
+    amount += mChunks[i].SizeOfExcludingThis(aMallocSizeOf, false);
+  }
+
+  amount += mUpmixChannels.SizeOfExcludingThis(aMallocSizeOf);
+  return amount;
+}
+
 void
 DelayBuffer::Write(const AudioChunk& aInputChunk)
 {
   // We must have a reference to the buffer if there are channels
   MOZ_ASSERT(aInputChunk.IsNull() == !aInputChunk.mChannelData.Length());
 #ifdef DEBUG
   MOZ_ASSERT(!mHaveWrittenBlock);
   mHaveWrittenBlock = true;
--- a/content/media/webaudio/DelayBuffer.h
+++ b/content/media/webaudio/DelayBuffer.h
@@ -70,16 +70,18 @@ public:
 
   void Reset() {
     mChunks.Clear();
     mCurrentDelay = -1.0;
   };
 
   int MaxDelayTicks() const { return mMaxDelayTicks; }
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+
 private:
   void ReadChannels(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
                     const AudioChunk* aOutputChunk,
                     uint32_t aFirstChannel, uint32_t aNumChannelsToRead,
                     ChannelInterpretation aChannelInterpretation);
   bool EnsureBuffer();
   int PositionForDelay(int aDelay);
   int ChunkForPosition(int aPosition);
--- a/content/media/webaudio/DelayNode.cpp
+++ b/content/media/webaudio/DelayNode.cpp
@@ -154,16 +154,32 @@ public:
   {
     if (mLeftOverData <= 0) {
       aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
     } else {
       UpdateOutputBlock(aOutput);
     }
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    // Not owned:
+    // - mSource - probably not owned
+    // - mDestination - probably not owned
+    // - mDelay - shares ref with AudioNode, don't count
+    amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
   AudioNodeStream* mSource;
   AudioNodeStream* mDestination;
   AudioParamTimeline mDelay;
   DelayBuffer mBuffer;
   double mMaxDelay;
   TrackTicks mLastOutputPosition;
   // How much data we have in our buffer which needs to be flushed out when our inputs
   // finish.
@@ -180,16 +196,30 @@ DelayNode::DelayNode(AudioContext* aCont
 {
   DelayNodeEngine* engine =
     new DelayNodeEngine(this, aContext->Destination(),
                         aContext->SampleRate() * aMaxDelay);
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
 }
 
+size_t
+DelayNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mDelay->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+DelayNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 DelayNode::WrapObject(JSContext* aCx)
 {
   return DelayNodeBinding::Wrap(aCx, this);
 }
 
 void
 DelayNode::SendDelayToStream(AudioNode* aNode)
--- a/content/media/webaudio/DelayNode.h
+++ b/content/media/webaudio/DelayNode.h
@@ -30,16 +30,24 @@ public:
     return mDelay;
   }
 
   virtual const DelayNode* AsDelayNode() const MOZ_OVERRIDE
   {
     return this;
   }
 
+  virtual const char* NodeType() const
+  {
+    return "DelayNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   static void SendDelayToStream(AudioNode* aNode);
   friend class DelayNodeEngine;
 
 private:
   nsRefPtr<AudioParam> mDelay;
 };
 
--- a/content/media/webaudio/DynamicsCompressorNode.cpp
+++ b/content/media/webaudio/DynamicsCompressorNode.cpp
@@ -125,16 +125,33 @@ public:
 
     AllocateAudioBlock(channelCount, aOutput);
     mCompressor->process(&aInput, aOutput, aInput.GetDuration());
 
     SendReductionParamToMainThread(aStream,
                                    mCompressor->parameterValue(DynamicsCompressor::ParamReduction));
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    // Not owned:
+    // - mSource (probably)
+    // - mDestination (probably)
+    // - Don't count the AudioParamTimelines, their inner refs are owned by the
+    // AudioNode.
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    amount += mCompressor->sizeOfIncludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   void SendReductionParamToMainThread(AudioNodeStream* aStream, float aReduction)
   {
     MOZ_ASSERT(!NS_IsMainThread());
 
     class Command : public nsRunnable
     {
     public:
@@ -200,16 +217,34 @@ DynamicsCompressorNode::DynamicsCompress
   , mRelease(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(),
                             SendReleaseToStream, 0.25f))
 {
   DynamicsCompressorNodeEngine* engine = new DynamicsCompressorNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
 }
 
+size_t
+DynamicsCompressorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mThreshold->SizeOfIncludingThis(aMallocSizeOf);
+  amount += mKnee->SizeOfIncludingThis(aMallocSizeOf);
+  amount += mRatio->SizeOfIncludingThis(aMallocSizeOf);
+  amount += mAttack->SizeOfIncludingThis(aMallocSizeOf);
+  amount += mRelease->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+DynamicsCompressorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 DynamicsCompressorNode::WrapObject(JSContext* aCx)
 {
   return DynamicsCompressorNodeBinding::Wrap(aCx, this);
 }
 
 void
 DynamicsCompressorNode::SendThresholdToStream(AudioNode* aNode)
--- a/content/media/webaudio/DynamicsCompressorNode.h
+++ b/content/media/webaudio/DynamicsCompressorNode.h
@@ -51,16 +51,24 @@ public:
   }
 
   // Called GetRelease to prevent clashing with the nsISupports::Release name
   AudioParam* GetRelease() const
   {
     return mRelease;
   }
 
+  virtual const char* NodeType() const
+  {
+    return "DynamicsCompressorNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   static void SendThresholdToStream(AudioNode* aNode);
   static void SendKneeToStream(AudioNode* aNode);
   static void SendRatioToStream(AudioNode* aNode);
   static void SendAttackToStream(AudioNode* aNode);
   static void SendReleaseToStream(AudioNode* aNode);
 
 private:
--- a/content/media/webaudio/FFTBlock.h
+++ b/content/media/webaudio/FFTBlock.h
@@ -123,16 +123,30 @@ public:
   {
     return mOutputBuffer[aIndex].r;
   }
   float ImagData(uint32_t aIndex) const
   {
     return mOutputBuffer[aIndex].i;
   }
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t amount = 0;
+    amount += aMallocSizeOf(mFFT);
+    amount += aMallocSizeOf(mIFFT);
+    amount += mOutputBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   FFTBlock(const FFTBlock& other) MOZ_DELETE;
   void operator=(const FFTBlock& other) MOZ_DELETE;
 
   void EnsureFFT()
   {
     if (!mFFT) {
       mFFT = kiss_fftr_alloc(mFFTSize, 0, nullptr, nullptr);
--- a/content/media/webaudio/GainNode.cpp
+++ b/content/media/webaudio/GainNode.cpp
@@ -96,16 +96,30 @@ public:
         const float* inputBuffer = static_cast<const float*> (aInput.mChannelData[channel]);
         float* buffer = static_cast<float*> (const_cast<void*>
                           (aOutput->mChannelData[channel]));
         AudioBlockCopyChannelWithScale(inputBuffer, computedGain, buffer);
       }
     }
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    // Not owned:
+    // - mSource (probably)
+    // - mDestination (probably)
+    // - mGain - Internal ref owned by AudioNode
+    return AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
   AudioNodeStream* mSource;
   AudioNodeStream* mDestination;
   AudioParamTimeline mGain;
 };
 
 GainNode::GainNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
@@ -114,16 +128,30 @@ GainNode::GainNode(AudioContext* aContex
   , mGain(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(),
                          SendGainToStream, 1.0f))
 {
   GainNodeEngine* engine = new GainNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
 }
 
+size_t
+GainNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mGain->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+GainNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 GainNode::WrapObject(JSContext* aCx)
 {
   return GainNodeBinding::Wrap(aCx, this);
 }
 
 void
 GainNode::SendGainToStream(AudioNode* aNode)
--- a/content/media/webaudio/GainNode.h
+++ b/content/media/webaudio/GainNode.h
@@ -25,16 +25,24 @@ public:
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   AudioParam* Gain() const
   {
     return mGain;
   }
 
+  virtual const char* NodeType() const
+  {
+    return "GainNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   static void SendGainToStream(AudioNode* aNode);
 
 private:
   nsRefPtr<AudioParam> mGain;
 };
 
 }
--- a/content/media/webaudio/MediaElementAudioSourceNode.h
+++ b/content/media/webaudio/MediaElementAudioSourceNode.h
@@ -14,14 +14,24 @@ namespace dom {
 
 class MediaElementAudioSourceNode : public MediaStreamAudioSourceNode
 {
 public:
   MediaElementAudioSourceNode(AudioContext* aContext,
                               DOMMediaStream* aStream);
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  virtual const char* NodeType() const
+  {
+    return "MediaElementAudioSourceNode";
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 };
 
 }
 }
 
 #endif
--- a/content/media/webaudio/MediaStreamAudioDestinationNode.cpp
+++ b/content/media/webaudio/MediaStreamAudioDestinationNode.cpp
@@ -43,16 +43,21 @@ public:
   {
     *aOutput = aInput;
     StreamBuffer::Track* track = mOutputStream->EnsureTrack(MEDIA_STREAM_DEST_TRACK_ID,
                                                             aStream->SampleRate());
     AudioSegment* segment = track->Get<AudioSegment>();
     segment->AppendAndConsumeChunk(aOutput);
   }
 
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   ProcessedMediaStream* mOutputStream;
 };
 
 // This callback is used to ensure that only the audio data for this track is audible
 static bool FilterAudioNodeStreamTrack(StreamBuffer::Track* aTrack)
 {
   return aTrack->GetID() == MEDIA_STREAM_DEST_TRACK_ID;
@@ -76,16 +81,32 @@ MediaStreamAudioDestinationNode::MediaSt
   mPort = tus->AllocateInputPort(mStream, 0);
 
   nsIDocument* doc = aContext->GetParentObject()->GetExtantDoc();
   if (doc) {
     mDOMStream->CombineWithPrincipal(doc->NodePrincipal());
   }
 }
 
+size_t
+MediaStreamAudioDestinationNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  // Future:
+  // - mDOMStream
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mPort->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+MediaStreamAudioDestinationNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 void
 MediaStreamAudioDestinationNode::DestroyMediaStream()
 {
   AudioNode::DestroyMediaStream();
   if (mPort) {
     mPort->Destroy();
     mPort = nullptr;
   }
--- a/content/media/webaudio/MediaStreamAudioDestinationNode.h
+++ b/content/media/webaudio/MediaStreamAudioDestinationNode.h
@@ -29,16 +29,24 @@ public:
 
   virtual void DestroyMediaStream() MOZ_OVERRIDE;
 
   DOMMediaStream* DOMStream() const
   {
     return mDOMStream;
   }
 
+  virtual const char* NodeType() const
+  {
+    return "MediaStreamAudioDestinationNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   nsRefPtr<DOMMediaStream> mDOMStream;
   nsRefPtr<MediaInputPort> mPort;
 };
 
 }
 }
 
--- a/content/media/webaudio/MediaStreamAudioSourceNode.cpp
+++ b/content/media/webaudio/MediaStreamAudioSourceNode.cpp
@@ -44,16 +44,32 @@ MediaStreamAudioSourceNode::MediaStreamA
                                                MediaInputPort::FLAG_BLOCK_INPUT);
   mInputStream->AddConsumerToKeepAlive(this);
 }
 
 MediaStreamAudioSourceNode::~MediaStreamAudioSourceNode()
 {
 }
 
+size_t
+MediaStreamAudioSourceNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  // Future:
+  // - mInputStream
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mInputPort->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+MediaStreamAudioSourceNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 void
 MediaStreamAudioSourceNode::DestroyMediaStream()
 {
   if (mInputPort) {
     mInputPort->Destroy();
     mInputPort = nullptr;
   }
   AudioNode::DestroyMediaStream();
--- a/content/media/webaudio/MediaStreamAudioSourceNode.h
+++ b/content/media/webaudio/MediaStreamAudioSourceNode.h
@@ -26,16 +26,24 @@ public:
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamAudioSourceNode, AudioNode)
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   virtual void DestroyMediaStream() MOZ_OVERRIDE;
 
   virtual uint16_t NumberOfInputs() const MOZ_OVERRIDE { return 0; }
 
+  virtual const char* NodeType() const
+  {
+    return "MediaStreamAudioSourceNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   nsRefPtr<MediaInputPort> mInputPort;
   nsRefPtr<DOMMediaStream> mInputStream;
 };
 
 }
 }
 
--- a/content/media/webaudio/OscillatorNode.cpp
+++ b/content/media/webaudio/OscillatorNode.cpp
@@ -458,16 +458,42 @@ public:
         ComputeCustom(output, ticks, start, end);
         break;
       default:
         ComputeSilence(aOutput);
     };
 
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+
+    // Not owned:
+    // - mSource
+    // - mDestination
+    // - mFrequency (internal ref owned by node)
+    // - mDetune (internal ref owned by node)
+
+    if (mCustom) {
+      amount += mCustom->SizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    if (mPeriodicWave) {
+      amount += mPeriodicWave->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
   DCBlocker mDCBlocker;
   AudioNodeStream* mSource;
   AudioNodeStream* mDestination;
   TrackTicks mStart;
   TrackTicks mStop;
   AudioParamTimeline mFrequency;
   AudioParamTimeline mDetune;
   OscillatorType mType;
@@ -505,16 +531,34 @@ OscillatorNode::OscillatorNode(AudioCont
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
   mStream->AddMainThreadListener(this);
 }
 
 OscillatorNode::~OscillatorNode()
 {
 }
 
+size_t
+OscillatorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+
+  // For now only report if we know for sure that it's not shared.
+  amount += mPeriodicWave->SizeOfExcludingThisIfNotShared(aMallocSizeOf);
+  amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf);
+  amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+OscillatorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 OscillatorNode::WrapObject(JSContext* aCx)
 {
   return OscillatorNodeBinding::Wrap(aCx, this);
 }
 
 void
 OscillatorNode::SendFrequencyToStream(AudioNode* aNode)
--- a/content/media/webaudio/OscillatorNode.h
+++ b/content/media/webaudio/OscillatorNode.h
@@ -112,16 +112,24 @@ public:
     mType = OscillatorType::Custom;
     SendTypeToStream();
   }
 
   IMPL_EVENT_HANDLER(ended)
 
   virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
 
+  virtual const char* NodeType() const
+  {
+    return "OscillatorNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   static void SendFrequencyToStream(AudioNode* aNode);
   static void SendDetuneToStream(AudioNode* aNode);
   void SendTypeToStream();
   void SendPeriodicWaveToStream();
 
 private:
   OscillatorType mType;
--- a/content/media/webaudio/PannerNode.cpp
+++ b/content/media/webaudio/PannerNode.cpp
@@ -183,16 +183,31 @@ public:
 
   void EqualPowerPanningFunction(const AudioChunk& aInput, AudioChunk* aOutput);
   void HRTFPanningFunction(const AudioChunk& aInput, AudioChunk* aOutput);
 
   float LinearGainFunction(float aDistance);
   float InverseGainFunction(float aDistance);
   float ExponentialGainFunction(float aDistance);
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    if (mHRTFPanner) {
+      amount += mHRTFPanner->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
   nsAutoPtr<HRTFPanner> mHRTFPanner;
   typedef void (PannerNodeEngine::*PanningModelFunction)(const AudioChunk& aInput, AudioChunk* aOutput);
   PanningModelFunction mPanningModelFunction;
   typedef float (PannerNodeEngine::*DistanceModelFunction)(float aDistance);
   DistanceModelFunction mDistanceModelFunction;
   ThreeDPoint mPosition;
   ThreeDPoint mOrientation;
   ThreeDPoint mVelocity;
@@ -237,16 +252,30 @@ PannerNode::PannerNode(AudioContext* aCo
 
 PannerNode::~PannerNode()
 {
   if (Context()) {
     Context()->UnregisterPannerNode(this);
   }
 }
 
+size_t
+PannerNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mSources.SizeOfExcludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+PannerNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 PannerNode::WrapObject(JSContext* aCx)
 {
   return PannerNodeBinding::Wrap(aCx, this);
 }
 
 void PannerNode::DestroyMediaStream()
 {
--- a/content/media/webaudio/PannerNode.h
+++ b/content/media/webaudio/PannerNode.h
@@ -240,16 +240,24 @@ public:
     SendDoubleParameterToStream(CONE_OUTER_GAIN, mConeOuterGain);
   }
 
   float ComputeDopplerShift();
   void SendDopplerToSourcesIfNeeded();
   void FindConnectedSources();
   void FindConnectedSources(AudioNode* aNode, nsTArray<AudioBufferSourceNode*>& aSources, std::set<AudioNode*>& aSeenNodes);
 
+  virtual const char* NodeType() const
+  {
+    return "PannerNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   friend class AudioListener;
   friend class PannerNodeEngine;
   enum EngineParameters {
     LISTENER_POSITION,
     LISTENER_FRONT_VECTOR, // unit length
     LISTENER_RIGHT_VECTOR, // unit length, orthogonal to LISTENER_FRONT_VECTOR
     LISTENER_VELOCITY,
--- a/content/media/webaudio/PeriodicWave.cpp
+++ b/content/media/webaudio/PeriodicWave.cpp
@@ -39,16 +39,29 @@ PeriodicWave::PeriodicWave(AudioContext*
     return;
   }
   PodCopy(buffer, aRealData, aLength);
   mCoefficients->SetData(0, buffer, buffer);
   PodCopy(buffer+aLength, aImagData, aLength);
   mCoefficients->SetData(1, nullptr, buffer+aLength);
 }
 
+size_t
+PeriodicWave::SizeOfExcludingThisIfNotShared(MallocSizeOf aMallocSizeOf) const
+{
+  // Not owned:
+  // - mContext
+  size_t amount = 0;
+  if (!mCoefficients->IsShared()) {
+    amount += mCoefficients->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  return amount;
+}
+
 JSObject*
 PeriodicWave::WrapObject(JSContext* aCx)
 {
   return PeriodicWaveBinding::Wrap(aCx, this);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/content/media/webaudio/PeriodicWave.h
+++ b/content/media/webaudio/PeriodicWave.h
@@ -42,16 +42,18 @@ public:
     return mLength;
   }
 
   ThreadSharedFloatArrayBufferList* GetThreadSharedBuffer() const
   {
     return mCoefficients;
   }
 
+  size_t SizeOfExcludingThisIfNotShared(MallocSizeOf aMallocSizeOf) const;
+
 private:
   nsRefPtr<AudioContext> mContext;
   nsRefPtr<ThreadSharedFloatArrayBufferList> mCoefficients;
   uint32_t mLength;
 };
 
 }
 }
--- a/content/media/webaudio/ScriptProcessorNode.cpp
+++ b/content/media/webaudio/ScriptProcessorNode.cpp
@@ -33,17 +33,29 @@ class SharedBuffers
 private:
   class OutputQueue
   {
   public:
     explicit OutputQueue(const char* aName)
       : mMutex(aName)
     {}
 
-    Mutex& Lock() { return mMutex; }
+    size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+    {
+      mMutex.AssertCurrentThreadOwns();
+
+      size_t amount = 0;
+      for (size_t i = 0; i < mBufferList.size(); i++) {
+        amount += mBufferList[i].SizeOfExcludingThis(aMallocSizeOf, false);
+      }
+
+      return amount;
+    }
+
+    Mutex& Lock() const { return const_cast<OutputQueue*>(this)->mMutex; }
 
     size_t ReadyToConsume() const
     {
       mMutex.AssertCurrentThreadOwns();
       MOZ_ASSERT(!NS_IsMainThread());
       return mBufferList.size();
     }
 
@@ -90,16 +102,28 @@ public:
     : mOutputQueue("SharedBuffers::outputQueue")
     , mDelaySoFar(TRACK_TICKS_MAX)
     , mSampleRate(aSampleRate)
     , mLatency(0.0)
     , mDroppingBuffers(false)
   {
   }
 
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t amount = aMallocSizeOf(this);
+
+    {
+      MutexAutoLock lock(mOutputQueue.Lock());
+      amount += mOutputQueue.SizeOfExcludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
   // main thread
   void FinishProducingOutputBuffer(ThreadSharedFloatArrayBufferList* aBuffer,
                                    uint32_t aBufferSize)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     TimeStamp now = TimeStamp::Now();
 
@@ -288,16 +312,36 @@ public:
     if (mInputWriteIndex >= mBufferSize) {
       SendBuffersToMainThread(aStream);
       mInputWriteIndex -= mBufferSize;
       mSeenNonSilenceInput = false;
       AllocateInputBlock();
     }
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    // Not owned:
+    // - mSharedBuffers
+    // - mSource (probably)
+    // - mDestination (probably)
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    amount += mInputChannels.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < mInputChannels.Length(); i++) {
+      amount += mInputChannels[i].SizeOfExcludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   void AllocateInputBlock()
   {
     for (unsigned i = 0; i < mInputChannels.Length(); ++i) {
       if (!mInputChannels[i]) {
         mInputChannels[i] = new float[mBufferSize];
       }
     }
@@ -445,16 +489,30 @@ ScriptProcessorNode::ScriptProcessorNode
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
 }
 
 ScriptProcessorNode::~ScriptProcessorNode()
 {
 }
 
+size_t
+ScriptProcessorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mSharedBuffers->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+ScriptProcessorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 ScriptProcessorNode::WrapObject(JSContext* aCx)
 {
   return ScriptProcessorNodeBinding::Wrap(aCx, this);
 }
 
 }
 }
--- a/content/media/webaudio/ScriptProcessorNode.h
+++ b/content/media/webaudio/ScriptProcessorNode.h
@@ -84,16 +84,24 @@ public:
 
   uint32_t NumberOfOutputChannels() const
   {
     return mNumberOfOutputChannels;
   }
 
   using DOMEventTargetHelper::DispatchTrustedEvent;
 
+  virtual const char* NodeType() const
+  {
+    return "ScriptProcessorNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   nsAutoPtr<SharedBuffers> mSharedBuffers;
   const uint32_t mBufferSize;
   const uint32_t mNumberOfOutputChannels;
 };
 
 }
 }
--- a/content/media/webaudio/WaveShaperNode.cpp
+++ b/content/media/webaudio/WaveShaperNode.cpp
@@ -125,16 +125,26 @@ public:
 
     WebAudioUtils::SpeexResamplerProcess(mDownSampler, aChannel,
                                          inputData, &inSamples,
                                          aOutputData, &outSamples);
 
     MOZ_ASSERT(inSamples == WEBAUDIO_BLOCK_SIZE*aBlocks && outSamples == WEBAUDIO_BLOCK_SIZE);
   }
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t amount = 0;
+    // Future: properly measure speex memory
+    amount += aMallocSizeOf(mUpSampler);
+    amount += aMallocSizeOf(mDownSampler);
+    amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
 private:
   void Destroy()
   {
     if (mUpSampler) {
       speex_resampler_destroy(mUpSampler);
       mUpSampler = nullptr;
     }
     if (mDownSampler) {
@@ -239,16 +249,29 @@ public:
         mResampler.DownSample(i, outputBuffer, 4);
         break;
       default:
         NS_NOTREACHED("We should never reach here");
       }
     }
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    amount += mCurve.SizeOfExcludingThis(aMallocSizeOf);
+    amount += mResampler.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   nsTArray<float> mCurve;
   OverSampleType mType;
   Resampler mResampler;
 };
 
 WaveShaperNode::WaveShaperNode(AudioContext* aContext)
   : AudioNode(aContext,
--- a/content/media/webaudio/WaveShaperNode.h
+++ b/content/media/webaudio/WaveShaperNode.h
@@ -34,16 +34,33 @@ public:
   void SetCurve(const Nullable<Float32Array>& aData);
 
   OverSampleType Oversample() const
   {
     return mType;
   }
   void SetOversample(OverSampleType aType);
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    // Possibly track in the future:
+    // - mCurve
+    return AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  virtual const char* NodeType() const
+  {
+    return "WaveShaperNode";
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   void ClearCurve();
 
 private:
   JS::Heap<JSObject*> mCurve;
   OverSampleType mType;
 };
 
--- a/content/media/webaudio/blink/DirectConvolver.h
+++ b/content/media/webaudio/blink/DirectConvolver.h
@@ -25,27 +25,36 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef DirectConvolver_h
 #define DirectConvolver_h
 
 #include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 class DirectConvolver {
 public:
     DirectConvolver(size_t inputBlockSize);
 
     void process(const nsTArray<float>* convolutionKernel, const float* sourceP, float* destP, size_t framesToProcess);
 
     void reset();
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+    {
+        size_t amount = aMallocSizeOf(this);
+        amount += m_buffer.SizeOfExcludingThis(aMallocSizeOf);
+        return amount;
+    }
+
+
 private:
     size_t m_inputBlockSize;
 
     nsTArray<float> m_buffer;
 };
 
 } // namespace WebCore
 
--- a/content/media/webaudio/blink/DynamicsCompressor.cpp
+++ b/content/media/webaudio/blink/DynamicsCompressor.cpp
@@ -47,16 +47,39 @@ DynamicsCompressor::DynamicsCompressor(f
     m_lastFilterStageRatio = -1;
     m_lastAnchor = -1;
     m_lastFilterStageGain = -1;
 
     setNumberOfChannels(numberOfChannels);
     initializeParameters();
 }
 
+size_t DynamicsCompressor::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+    amount += m_preFilterPacks.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_preFilterPacks.Length(); i++) {
+        if (m_preFilterPacks[i]) {
+            amount += m_preFilterPacks[i]->sizeOfIncludingThis(aMallocSizeOf);
+        }
+    }
+
+    amount += m_postFilterPacks.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_postFilterPacks.Length(); i++) {
+        if (m_postFilterPacks[i]) {
+            amount += m_postFilterPacks[i]->sizeOfIncludingThis(aMallocSizeOf);
+        }
+    }
+
+    amount += m_sourceChannels.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_destinationChannels.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_compressor.sizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+}
+
 void DynamicsCompressor::setParameterValue(unsigned parameterID, float value)
 {
     MOZ_ASSERT(parameterID < ParamLast);
     if (parameterID < ParamLast)
         m_parameters[parameterID] = value;
 }
 
 void DynamicsCompressor::initializeParameters()
--- a/content/media/webaudio/blink/DynamicsCompressor.h
+++ b/content/media/webaudio/blink/DynamicsCompressor.h
@@ -29,16 +29,17 @@
 #ifndef DynamicsCompressor_h
 #define DynamicsCompressor_h
 
 #include "DynamicsCompressorKernel.h"
 #include "ZeroPole.h"
 
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace mozilla {
 struct AudioChunk;
 }
 
 namespace WebCore {
 
 using mozilla::AudioChunk;
@@ -81,32 +82,38 @@ public:
     float parameterValue(unsigned parameterID);
 
     float sampleRate() const { return m_sampleRate; }
     float nyquist() const { return m_sampleRate / 2; }
 
     double tailTime() const { return 0; }
     double latencyTime() const { return m_compressor.latencyFrames() / static_cast<double>(sampleRate()); }
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 protected:
     unsigned m_numberOfChannels;
 
     // m_parameters holds the tweakable compressor parameters.
     float m_parameters[ParamLast];
     void initializeParameters();
 
     float m_sampleRate;
 
     // Emphasis filter controls.
     float m_lastFilterStageRatio;
     float m_lastAnchor;
     float m_lastFilterStageGain;
 
     typedef struct {
         ZeroPole filters[4];
+        size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+        {
+            return aMallocSizeOf(this);
+        }
     } ZeroPoleFilterPack4;
 
     // Per-channel emphasis filters.
     nsTArray<nsAutoPtr<ZeroPoleFilterPack4> > m_preFilterPacks;
     nsTArray<nsAutoPtr<ZeroPoleFilterPack4> > m_postFilterPacks;
 
     nsAutoArrayPtr<const float*> m_sourceChannels;
     nsAutoArrayPtr<float*> m_destinationChannels;
--- a/content/media/webaudio/blink/DynamicsCompressorKernel.cpp
+++ b/content/media/webaudio/blink/DynamicsCompressorKernel.cpp
@@ -68,16 +68,27 @@ DynamicsCompressorKernel::DynamicsCompre
 
     // Initializes most member variables
     reset();
 
     m_meteringReleaseK =
         static_cast<float>(WebAudioUtils::DiscreteTimeConstantForSampleRate(meteringReleaseTimeConstant, sampleRate));
 }
 
+size_t DynamicsCompressorKernel::sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = 0;
+    amount += m_preDelayBuffers.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_preDelayBuffers.Length(); i++) {
+        amount += m_preDelayBuffers[i].SizeOfExcludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+}
+
 void DynamicsCompressorKernel::setNumberOfChannels(unsigned numberOfChannels)
 {
     if (m_preDelayBuffers.Length() == numberOfChannels)
         return;
 
     m_preDelayBuffers.Clear();
     for (unsigned i = 0; i < numberOfChannels; ++i)
         m_preDelayBuffers.AppendElement(new float[MaxPreDelayFrames]);
--- a/content/media/webaudio/blink/DynamicsCompressorKernel.h
+++ b/content/media/webaudio/blink/DynamicsCompressorKernel.h
@@ -26,16 +26,17 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef DynamicsCompressorKernel_h
 #define DynamicsCompressorKernel_h
 
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 class DynamicsCompressorKernel {
 public:
     DynamicsCompressorKernel(float sampleRate, unsigned numberOfChannels);
 
     void setNumberOfChannels(unsigned);
@@ -64,16 +65,18 @@ public:
     void reset();
 
     unsigned latencyFrames() const { return m_lastPreDelayFrames; }
 
     float sampleRate() const { return m_sampleRate; }
 
     float meteringGain() const { return m_meteringGain; }
 
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 protected:
     float m_sampleRate;
 
     float m_detectorAverage;
     float m_compressorGain;
 
     // Metering
     float m_meteringReleaseK;
--- a/content/media/webaudio/blink/FFTConvolver.cpp
+++ b/content/media/webaudio/blink/FFTConvolver.cpp
@@ -40,16 +40,31 @@ FFTConvolver::FFTConvolver(size_t fftSiz
   m_inputBuffer.SetLength(fftSize);
   PodZero(m_inputBuffer.Elements(), fftSize);
   m_outputBuffer.SetLength(fftSize);
   PodZero(m_outputBuffer.Elements(), fftSize);
   m_lastOverlapBuffer.SetLength(fftSize / 2);
   PodZero(m_lastOverlapBuffer.Elements(), fftSize / 2);
 }
 
+size_t FFTConvolver::sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = 0;
+    amount += m_frame.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_inputBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_outputBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_lastOverlapBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+}
+
+size_t FFTConvolver::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + sizeOfExcludingThis(aMallocSizeOf);
+}
+
 void FFTConvolver::process(FFTBlock* fftKernel, const float* sourceP, float* destP, size_t framesToProcess)
 {
     size_t halfSize = fftSize() / 2;
 
     // framesToProcess must be an exact multiple of halfSize,
     // or halfSize is a multiple of framesToProcess when halfSize > framesToProcess.
     bool isGood = !(halfSize % framesToProcess && framesToProcess % halfSize);
     MOZ_ASSERT(isGood);
--- a/content/media/webaudio/blink/FFTConvolver.h
+++ b/content/media/webaudio/blink/FFTConvolver.h
@@ -26,16 +26,17 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef FFTConvolver_h
 #define FFTConvolver_h
 
 #include "nsTArray.h"
 #include "mozilla/FFTBlock.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 typedef nsTArray<float> AudioFloatArray;
 using mozilla::FFTBlock;
 
 class FFTConvolver {
 public:
@@ -52,16 +53,19 @@ public:
     //
     // Processing in-place is allowed...
     void process(FFTBlock* fftKernel, const float* sourceP, float* destP, size_t framesToProcess);
 
     void reset();
 
     size_t fftSize() const { return m_frame.FFTSize(); }
 
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     FFTBlock m_frame;
 
     // Buffer input until we get fftSize / 2 samples then do an FFT
     size_t m_readWriteIndex;
     AudioFloatArray m_inputBuffer;
 
     // Stores output which we read a little at a time
--- a/content/media/webaudio/blink/HRTFDatabase.cpp
+++ b/content/media/webaudio/blink/HRTFDatabase.cpp
@@ -74,16 +74,27 @@ HRTFDatabase::HRTFDatabase(float sampleR
                 float x = static_cast<float>(jj) / static_cast<float>(InterpolationFactor);
                 m_elevations[i + jj] = HRTFElevation::createByInterpolatingSlices(m_elevations[i].get(), m_elevations[j].get(), x, sampleRate);
                 MOZ_ASSERT(m_elevations[i + jj].get());
             }
         }
     }
 }
 
+size_t HRTFDatabase::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+    amount += m_elevations.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_elevations.Length(); i++) {
+      amount += m_elevations[i]->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+}
+
 void HRTFDatabase::getKernelsFromAzimuthElevation(double azimuthBlend, unsigned azimuthIndex, double elevationAngle, HRTFKernel* &kernelL, HRTFKernel* &kernelR,
                                                   double& frameDelayL, double& frameDelayR)
 {
     unsigned elevationIndex = indexFromElevationAngle(elevationAngle);
     MOZ_ASSERT(elevationIndex < m_elevations.Length() && m_elevations.Length() > 0);
     
     if (!m_elevations.Length()) {
         kernelL = 0;
--- a/content/media/webaudio/blink/HRTFDatabase.h
+++ b/content/media/webaudio/blink/HRTFDatabase.h
@@ -27,16 +27,17 @@
  */
 
 #ifndef HRTFDatabase_h
 #define HRTFDatabase_h
 
 #include "HRTFElevation.h"
 #include "nsAutoRef.h"
 #include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 class HRTFKernel;
 
 class HRTFDatabase {
 public:
     static nsReturnRef<HRTFDatabase> create(float sampleRate);
@@ -50,16 +51,18 @@ public:
     // Returns the number of different azimuth angles.
     static unsigned numberOfAzimuths() { return HRTFElevation::NumberOfTotalAzimuths; }
 
     float sampleRate() const { return m_sampleRate; }
 
     // Number of elevations loaded from resource.
     static const unsigned NumberOfRawElevations;
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     HRTFDatabase(const HRTFDatabase& other) MOZ_DELETE;
     void operator=(const HRTFDatabase& other) MOZ_DELETE;
 
     explicit HRTFDatabase(float sampleRate);
 
     // Minimum and maximum elevation angles (inclusive) for a HRTFDatabase.
     static const int MinElevation;
--- a/content/media/webaudio/blink/HRTFDatabaseLoader.cpp
+++ b/content/media/webaudio/blink/HRTFDatabaseLoader.cpp
@@ -83,16 +83,30 @@ HRTFDatabaseLoader::~HRTFDatabaseLoader(
         s_loaderMap->RemoveEntry(m_databaseSampleRate);
         if (s_loaderMap->Count() == 0) {
             delete s_loaderMap;
             s_loaderMap = nullptr;
         }
     }
 }
 
+size_t HRTFDatabaseLoader::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+
+    // NB: Need to make sure we're not competing with the loader thread.
+    const_cast<HRTFDatabaseLoader*>(this)->waitForLoaderThreadCompletion();
+
+    if (m_hrtfDatabase) {
+        amount += m_hrtfDatabase->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+}
+
 class HRTFDatabaseLoader::ProxyReleaseEvent MOZ_FINAL : public nsRunnable {
 public:
     explicit ProxyReleaseEvent(HRTFDatabaseLoader* loader) : mLoader(loader) {}
     NS_IMETHOD Run() MOZ_OVERRIDE
     {
         mLoader->MainThreadRelease();
         return NS_OK;
     }
--- a/content/media/webaudio/blink/HRTFDatabaseLoader.h
+++ b/content/media/webaudio/blink/HRTFDatabaseLoader.h
@@ -26,16 +26,17 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef HRTFDatabaseLoader_h
 #define HRTFDatabaseLoader_h
 
 #include "nsHashKeys.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/MemoryReporting.h"
 #include "mozilla/Mutex.h"
 #include "HRTFDatabase.h"
 
 template <class EntryType> class nsTHashtable;
 template <class T> class nsAutoRef;
 
 namespace WebCore {
 
@@ -88,16 +89,18 @@ public:
 
     float databaseSampleRate() const { return m_databaseSampleRate; }
 
     static void shutdown();
     
     // Called in asynchronous loading thread.
     void load();
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     // Both constructor and destructor must be called from the main thread.
     explicit HRTFDatabaseLoader(float sampleRate);
     ~HRTFDatabaseLoader();
     
     void ProxyRelease(); // any thread
     void MainThreadRelease(); // main thread only
     class ProxyReleaseEvent;
--- a/content/media/webaudio/blink/HRTFElevation.cpp
+++ b/content/media/webaudio/blink/HRTFElevation.cpp
@@ -45,16 +45,28 @@ const int numberOfElevations = MOZ_ARRAY
 
 const unsigned HRTFElevation::NumberOfTotalAzimuths = 360 / 15 * 8;
 
 const int rawSampleRate = irc_composite_c_r0195_sample_rate;
 
 // Number of frames in an individual impulse response.
 const size_t ResponseFrameSize = 256;
 
+size_t HRTFElevation::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+
+    amount += m_kernelListL.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_kernelListL.Length(); i++) {
+        amount += m_kernelListL[i]->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+}
+
 size_t HRTFElevation::fftSizeForSampleRate(float sampleRate)
 {
     // The IRCAM HRTF impulse responses were 512 sample-frames @44.1KHz,
     // but these have been truncated to 256 samples.
     // An FFT-size of twice impulse response size is used (for convolution).
     // So for sample rates of 44.1KHz an FFT size of 512 is good.
     // We double the FFT-size only for sample rates at least double this.
     // If the FFT size is too large then the impulse response will be padded
--- a/content/media/webaudio/blink/HRTFElevation.h
+++ b/content/media/webaudio/blink/HRTFElevation.h
@@ -26,16 +26,17 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef HRTFElevation_h
 #define HRTFElevation_h
 
 #include "HRTFKernel.h"
 #include "nsAutoRef.h"
+#include "mozilla/MemoryReporting.h"
 
 struct SpeexResamplerState_;
 typedef struct SpeexResamplerState_ SpeexResamplerState;
 
 namespace WebCore {
 
 // HRTFElevation contains all of the HRTFKernels (one left ear and one right ear per azimuth angle) for a particular elevation.
 
@@ -58,16 +59,18 @@ public:
     // The interpolated delays based on azimuthBlend: 0 -> 1 are returned in frameDelayL and frameDelayR.
     void getKernelsFromAzimuth(double azimuthBlend, unsigned azimuthIndex, HRTFKernel* &kernelL, HRTFKernel* &kernelR, double& frameDelayL, double& frameDelayR);
     
     // Total number of azimuths after interpolation.
     static const unsigned NumberOfTotalAzimuths;
 
     static size_t fftSizeForSampleRate(float sampleRate);
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     HRTFElevation(const HRTFElevation& other) MOZ_DELETE;
     void operator=(const HRTFElevation& other) MOZ_DELETE;
 
     HRTFElevation(HRTFKernelList *kernelListL, int elevation, float sampleRate)
         : m_elevationAngle(elevation)
         , m_sampleRate(sampleRate)
     {
--- a/content/media/webaudio/blink/HRTFKernel.h
+++ b/content/media/webaudio/blink/HRTFKernel.h
@@ -28,16 +28,17 @@
 
 #ifndef HRTFKernel_h
 #define HRTFKernel_h
 
 #include "nsAutoPtr.h"
 #include "nsAutoRef.h"
 #include "nsTArray.h"
 #include "mozilla/FFTBlock.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 using mozilla::FFTBlock;
 
 // HRTF stands for Head-Related Transfer Function.
 // HRTFKernel is a frequency-domain representation of an impulse-response used as part of the spatialized panning system.
 // For a given azimuth / elevation angle there will be one HRTFKernel for the left ear transfer function, and one for the right ear.
@@ -59,16 +60,23 @@ public:
     FFTBlock* fftFrame() { return m_fftFrame.get(); }
     
     size_t fftSize() const { return m_fftFrame->FFTSize(); }
     float frameDelay() const { return m_frameDelay; }
 
     float sampleRate() const { return m_sampleRate; }
     double nyquist() const { return 0.5 * sampleRate(); }
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+    {
+        size_t amount = aMallocSizeOf(this);
+        amount += m_fftFrame->SizeOfIncludingThis(aMallocSizeOf);
+        return amount;
+    }
+
 private:
     HRTFKernel(const HRTFKernel& other) MOZ_DELETE;
     void operator=(const HRTFKernel& other) MOZ_DELETE;
 
     // Note: this is destructive on the passed in |impulseResponse|.
     HRTFKernel(float* impulseResponse, size_t fftSize, float sampleRate);
     
     HRTFKernel(nsAutoPtr<FFTBlock> fftFrame, float frameDelay, float sampleRate)
--- a/content/media/webaudio/blink/HRTFPanner.cpp
+++ b/content/media/webaudio/blink/HRTFPanner.cpp
@@ -65,16 +65,37 @@ HRTFPanner::HRTFPanner(float sampleRate,
     m_tempR2.SetLength(RenderingQuantum);
 }
 
 HRTFPanner::~HRTFPanner()
 {
     MOZ_COUNT_DTOR(HRTFPanner);
 }
 
+size_t HRTFPanner::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+
+    if (m_databaseLoader) {
+        m_databaseLoader->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    amount += m_convolverL1.sizeOfExcludingThis(aMallocSizeOf);
+    amount += m_convolverR1.sizeOfExcludingThis(aMallocSizeOf);
+    amount += m_convolverL2.sizeOfExcludingThis(aMallocSizeOf);
+    amount += m_convolverR2.sizeOfExcludingThis(aMallocSizeOf);
+    amount += m_delayLine.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_tempL1.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_tempL2.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_tempR1.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_tempR2.SizeOfExcludingThis(aMallocSizeOf);
+
+    return amount;
+}
+
 void HRTFPanner::reset()
 {
     m_azimuthIndex1 = UninitializedAzimuth;
     m_azimuthIndex2 = UninitializedAzimuth;
     // m_elevation1 and m_elevation2 are initialized in pan()
     m_crossfadeSelection = CrossfadeSelection1;
     m_crossfadeX = 0.0f;
     m_crossfadeIncr = 0.0f;
--- a/content/media/webaudio/blink/HRTFPanner.h
+++ b/content/media/webaudio/blink/HRTFPanner.h
@@ -22,16 +22,17 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef HRTFPanner_h
 #define HRTFPanner_h
 
 #include "FFTConvolver.h"
 #include "DelayBuffer.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace mozilla {
 struct AudioChunk;
 }
 
 namespace WebCore {
 
 class HRTFDatabaseLoader;
@@ -48,16 +49,18 @@ public:
     void reset();
 
     size_t fftSize() const { return m_convolverL1.fftSize(); }
 
     float sampleRate() const { return m_sampleRate; }
 
     int maxTailFrames() const;
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     // Given an azimuth angle in the range -180 -> +180, returns the corresponding azimuth index for the database,
     // and azimuthBlend which is an interpolation value from 0 -> 1.
     int calculateDesiredAzimuthIndexAndBlend(double azimuth, double& azimuthBlend);
 
     mozilla::RefPtr<HRTFDatabaseLoader> m_databaseLoader;
 
     float m_sampleRate;
--- a/content/media/webaudio/blink/PeriodicWave.cpp
+++ b/content/media/webaudio/blink/PeriodicWave.cpp
@@ -90,16 +90,30 @@ PeriodicWave::PeriodicWave(float sampleR
     , m_numberOfRanges(NumberOfRanges)
     , m_centsPerRange(CentsPerRange)
 {
     float nyquist = 0.5 * m_sampleRate;
     m_lowestFundamentalFrequency = nyquist / maxNumberOfPartials();
     m_rateScale = m_periodicWaveSize / m_sampleRate;
 }
 
+size_t PeriodicWave::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+
+    amount += m_bandLimitedTables.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_bandLimitedTables.Length(); i++) {
+        if (m_bandLimitedTables[i]) {
+            amount += m_bandLimitedTables[i]->SizeOfIncludingThis(aMallocSizeOf);
+        }
+    }
+
+    return amount;
+}
+
 void PeriodicWave::waveDataForFundamentalFrequency(float fundamentalFrequency, float* &lowerWaveData, float* &higherWaveData, float& tableInterpolationFactor)
 {
     // Negative frequencies are allowed, in which case we alias
     // to the positive frequency.
     fundamentalFrequency = fabsf(fundamentalFrequency);
 
     // Calculate the pitch range.
     float ratio = fundamentalFrequency > 0 ? fundamentalFrequency / m_lowestFundamentalFrequency : 0.5;
--- a/content/media/webaudio/blink/PeriodicWave.h
+++ b/content/media/webaudio/blink/PeriodicWave.h
@@ -27,16 +27,17 @@
  */
 
 #ifndef PeriodicWave_h
 #define PeriodicWave_h
 
 #include "mozilla/dom/OscillatorNodeBinding.h"
 #include <nsAutoPtr.h>
 #include <nsTArray.h>
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 typedef nsTArray<float> AudioFloatArray;
 
 class PeriodicWave {
 public:
     static PeriodicWave* createSine(float sampleRate);
@@ -63,16 +64,18 @@ public:
 
     // Returns the scalar multiplier to the oscillator frequency to calculate
     // wave buffer phase increment.
     float rateScale() const { return m_rateScale; }
 
     unsigned periodicWaveSize() const { return m_periodicWaveSize; }
     float sampleRate() const { return m_sampleRate; }
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     explicit PeriodicWave(float sampleRate);
 
     void generateBasicWaveform(mozilla::dom::OscillatorType);
 
     float m_sampleRate;
     unsigned m_periodicWaveSize;
     unsigned m_numberOfRanges;
--- a/content/media/webaudio/blink/Reverb.cpp
+++ b/content/media/webaudio/blink/Reverb.cpp
@@ -100,16 +100,31 @@ Reverb::Reverb(ThreadSharedFloatArrayBuf
             }
         }
     }
 
     initialize(irChannels, impulseResponseBufferLength, renderSliceSize,
                maxFFTSize, numberOfChannels, useBackgroundThreads);
 }
 
+size_t Reverb::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+    amount += m_convolvers.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_convolvers.Length(); i++) {
+        if (m_convolvers[i]) {
+            amount += m_convolvers[i]->sizeOfIncludingThis(aMallocSizeOf);
+        }
+    }
+
+    amount += m_tempBuffer.SizeOfExcludingThis(aMallocSizeOf, false);
+    return amount;
+}
+
+
 void Reverb::initialize(const nsTArray<const float*>& impulseResponseBuffer,
                         size_t impulseResponseBufferLength, size_t renderSliceSize,
                         size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads)
 {
     m_impulseResponseLength = impulseResponseBufferLength;
 
     // The reverb can handle a mono impulse response and still do stereo processing
     size_t numResponseChannels = impulseResponseBuffer.Length();
--- a/content/media/webaudio/blink/Reverb.h
+++ b/content/media/webaudio/blink/Reverb.h
@@ -28,16 +28,17 @@
 
 #ifndef Reverb_h
 #define Reverb_h
 
 #include "ReverbConvolver.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 #include "AudioSegment.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace mozilla {
 class ThreadSharedFloatArrayBufferList;
 }
 
 namespace WebCore {
 
 class DirectConvolver;
@@ -53,16 +54,18 @@ public:
     Reverb(mozilla::ThreadSharedFloatArrayBufferList* impulseResponseBuffer, size_t impulseResponseBufferLength, size_t renderSliceSize, size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads, bool normalize, float sampleRate);
 
     void process(const mozilla::AudioChunk* sourceBus, mozilla::AudioChunk* destinationBus, size_t framesToProcess);
     void reset();
 
     size_t impulseResponseLength() const { return m_impulseResponseLength; }
     size_t latencyFrames() const;
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     void initialize(const nsTArray<const float*>& impulseResponseBuffer, size_t impulseResponseBufferLength, size_t renderSliceSize, size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads);
 
     size_t m_impulseResponseLength;
 
     nsTArray<nsAutoPtr<ReverbConvolver> > m_convolvers;
 
     // For "True" stereo processing
--- a/content/media/webaudio/blink/ReverbAccumulationBuffer.h
+++ b/content/media/webaudio/blink/ReverbAccumulationBuffer.h
@@ -25,16 +25,17 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef ReverbAccumulationBuffer_h
 #define ReverbAccumulationBuffer_h
 
 #include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 typedef nsTArray<float> AudioFloatArray;
 
 // ReverbAccumulationBuffer is a circular delay buffer with one client reading from it and multiple clients
 // writing/accumulating to it at different delay offsets from the read position.  The read operation will zero the memory
 // just read from the buffer, so it will be ready for accumulation the next time around.
@@ -53,16 +54,21 @@ public:
 
     size_t readIndex() const { return m_readIndex; }
     void updateReadIndex(int* readIndex, size_t numberOfFrames) const;
 
     size_t readTimeFrame() const { return m_readTimeFrame; }
 
     void reset();
 
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+    {
+        return m_buffer.SizeOfExcludingThis(aMallocSizeOf);
+    }
+
 private:
     AudioFloatArray m_buffer;
     size_t m_readIndex;
     size_t m_readTimeFrame; // for debugging (frame on continuous timeline)
 };
 
 } // namespace WebCore
 
--- a/content/media/webaudio/blink/ReverbConvolver.cpp
+++ b/content/media/webaudio/blink/ReverbConvolver.cpp
@@ -146,16 +146,45 @@ ReverbConvolver::~ReverbConvolver()
             m_moreInputBuffered = true;
             m_backgroundThreadCondition.Signal();
         }
 
         m_backgroundThread.Stop();
     }
 }
 
+size_t ReverbConvolver::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+    amount += m_stages.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_stages.Length(); i++) {
+        if (m_stages[i]) {
+            amount += m_stages[i]->sizeOfIncludingThis(aMallocSizeOf);
+        }
+    }
+
+    amount += m_backgroundStages.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_backgroundStages.Length(); i++) {
+        if (m_backgroundStages[i]) {
+            amount += m_backgroundStages[i]->sizeOfIncludingThis(aMallocSizeOf);
+        }
+    }
+
+    // NB: The buffer sizes are static, so even though they might be accessed
+    //     in another thread it's safe to measure them.
+    amount += m_accumulationBuffer.sizeOfExcludingThis(aMallocSizeOf);
+    amount += m_inputBuffer.sizeOfExcludingThis(aMallocSizeOf);
+
+    // Possible future measurements:
+    // - m_backgroundThread
+    // - m_backgroundThreadLock
+    // - m_backgroundThreadCondition
+    return amount;
+}
+
 void ReverbConvolver::backgroundThreadEntry()
 {
     while (!m_wantsToExit) {
         // Wait for realtime thread to give us more input
         m_moreInputBuffered = false;
         {
             AutoLock locker(m_backgroundThreadLock);
             while (!m_moreInputBuffered && !m_wantsToExit)
--- a/content/media/webaudio/blink/ReverbConvolver.h
+++ b/content/media/webaudio/blink/ReverbConvolver.h
@@ -27,16 +27,17 @@
  */
 
 #ifndef ReverbConvolver_h
 #define ReverbConvolver_h
 
 #include "ReverbAccumulationBuffer.h"
 #include "ReverbInputBuffer.h"
 #include "nsAutoPtr.h"
+#include "mozilla/MemoryReporting.h"
 #ifdef LOG
 #undef LOG
 #endif
 #include "base/condition_variable.h"
 #include "base/lock.h"
 #include "base/thread.h"
 
 namespace WebCore {
@@ -60,16 +61,18 @@ public:
     size_t impulseResponseLength() const { return m_impulseResponseLength; }
 
     ReverbInputBuffer* inputBuffer() { return &m_inputBuffer; }
 
     bool useBackgroundThreads() const { return m_useBackgroundThreads; }
     void backgroundThreadEntry();
 
     size_t latencyFrames() const;
+
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 private:
     nsTArray<nsAutoPtr<ReverbConvolverStage> > m_stages;
     nsTArray<nsAutoPtr<ReverbConvolverStage> > m_backgroundStages;
     size_t m_impulseResponseLength;
 
     ReverbAccumulationBuffer m_accumulationBuffer;
 
     // One or more background threads read from this input buffer which is fed from the realtime thread.
--- a/content/media/webaudio/blink/ReverbConvolverStage.cpp
+++ b/content/media/webaudio/blink/ReverbConvolverStage.cpp
@@ -82,16 +82,39 @@ ReverbConvolverStage::ReverbConvolverSta
     m_framesProcessed = 0; // total frames processed so far
 
     size_t delayBufferSize = m_preDelayLength < fftSize ? fftSize : m_preDelayLength;
     delayBufferSize = delayBufferSize < renderSliceSize ? renderSliceSize : delayBufferSize;
     m_preDelayBuffer.SetLength(delayBufferSize);
     PodZero(m_preDelayBuffer.Elements(), m_preDelayBuffer.Length());
 }
 
+size_t ReverbConvolverStage::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+
+    if (m_fftKernel) {
+        amount += m_fftKernel->SizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    if (m_fftConvolver) {
+        amount += m_fftConvolver->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    amount += m_preDelayBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_temporaryBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_directKernel.SizeOfExcludingThis(aMallocSizeOf);
+
+    if (m_directConvolver) {
+        amount += m_directConvolver->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+}
+
 void ReverbConvolverStage::processInBackground(ReverbConvolver* convolver, size_t framesToProcess)
 {
     ReverbInputBuffer* inputBuffer = convolver->inputBuffer();
     float* source = inputBuffer->directReadFrom(&m_inputReadIndex, framesToProcess);
     process(source, framesToProcess);
 }
 
 void ReverbConvolverStage::process(const float* source, size_t framesToProcess)
--- a/content/media/webaudio/blink/ReverbConvolverStage.h
+++ b/content/media/webaudio/blink/ReverbConvolverStage.h
@@ -29,16 +29,17 @@
 #ifndef ReverbConvolverStage_h
 #define ReverbConvolverStage_h
 
 #include "DirectConvolver.h"
 #include "FFTConvolver.h"
 
 #include "nsTArray.h"
 #include "mozilla/FFTBlock.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 using mozilla::FFTBlock;
 
 class ReverbAccumulationBuffer;
 class ReverbConvolver;
 
@@ -55,16 +56,18 @@ public:
 
     void processInBackground(ReverbConvolver* convolver, size_t framesToProcess);
 
     void reset();
 
     // Useful for background processing
     int inputReadIndex() const { return m_inputReadIndex; }
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     nsAutoPtr<FFTBlock> m_fftKernel;
     nsAutoPtr<FFTConvolver> m_fftConvolver;
 
     nsTArray<float> m_preDelayBuffer;
 
     ReverbAccumulationBuffer* m_accumulationBuffer;
     int m_accumulationReadIndex;
--- a/content/media/webaudio/blink/ReverbInputBuffer.h
+++ b/content/media/webaudio/blink/ReverbInputBuffer.h
@@ -25,16 +25,17 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef ReverbInputBuffer_h
 #define ReverbInputBuffer_h
 
 #include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 // ReverbInputBuffer is used to buffer input samples for deferred processing by the background threads.
 class ReverbInputBuffer {
 public:
     ReverbInputBuffer(size_t length);
 
@@ -49,16 +50,22 @@ public:
     // The individual background threads read here (and hope that they can keep up with the buffer writing).
     // readIndex is updated with the next readIndex to read from...
     // The assumption is that the buffer's length is evenly divisible by numberOfFrames.
     // FIXME: remove numberOfFrames restriction...
     float* directReadFrom(int* readIndex, size_t numberOfFrames);
 
     void reset();
 
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+    {
+        return m_buffer.SizeOfExcludingThis(aMallocSizeOf);
+    }
+
+
 private:
     nsTArray<float> m_buffer;
     size_t m_writeIndex;
 };
 
 } // namespace WebCore
 
 #endif // ReverbInputBuffer_h
--- a/content/svg/content/src/SVGMotionSMILAnimationFunction.cpp
+++ b/content/svg/content/src/SVGMotionSMILAnimationFunction.cpp
@@ -372,30 +372,26 @@ SVGMotionSMILAnimationFunction::CheckKey
   if (!HasAttr(nsGkAtoms::keyPoints))
     return;
 
   // attribute is ignored for calcMode="paced" (even if it's got errors)
   if (GetCalcMode() == CALC_PACED) {
     SetKeyPointsErrorFlag(false);
   }
 
-  if (mKeyPoints.IsEmpty()) {
-    // keyPoints attr is set, but array is empty => it failed preliminary checks
+  if (mKeyPoints.Length() != mKeyTimes.Length()) {
+    // there must be exactly as many keyPoints as keyTimes
     SetKeyPointsErrorFlag(true);
     return;
   }
 
   // Nothing else to check -- we can catch all keyPoints errors elsewhere.
   // -  Formatting & range issues will be caught in SetKeyPoints, and will
   //  result in an empty mKeyPoints array, which will drop us into the error
   //  case above.
-  // -  Number-of-entries issues will be caught in CheckKeyTimes (and flagged
-  //  as a problem with |keyTimes|), since we use our keyPoints entries to
-  //  populate the "values" list, and that list's count gets passed to
-  //  CheckKeyTimes.
 }
 
 nsresult
 SVGMotionSMILAnimationFunction::SetKeyPoints(const nsAString& aKeyPoints,
                                              nsAttrValue& aResult)
 {
   mKeyPoints.Clear();
   aResult.SetTo(aKeyPoints);
--- a/dom/apps/tests/test_app_update.html
+++ b/dom/apps/tests/test_app_update.html
@@ -13,17 +13,16 @@ https://bugzilla.mozilla.org/show_bug.cg
   /** Test for Bug 826058 **/
 
   SimpleTest.waitForExplicitFinish();
 
   var gBaseURL = 'http://test/tests/dom/apps/tests/';
   var gHostedManifestURL = gBaseURL + 'file_app.sjs?apptype=hosted&getmanifest=true';
   var gCachedManifestURL = gBaseURL + 'file_app.sjs?apptype=cached&getmanifest=true';
   var gGenerator = runTest();
-  var launchableValue = undefined;
 
   function go() {
     SpecialPowers.pushPermissions(
       [{ "type": "browser", "allow": 1, "context": document },
        { "type": "embed-apps", "allow": 1, "context": document },
        { "type": "webapps-manage", "allow": 1, "context": document }],
       function() { gGenerator.next() });
   }
@@ -51,17 +50,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   function xhrAbort(url) {
     ok(false, "XHR abort loading " + url);
     finish();
   }
 
   function runTest() {
     // Set up.
 
-    launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
 
     // Test Bug 927699 - navigator.mozApps.install(url) lets NS_ERROR_FAILURE
     //                   onto the web
     var request = navigator.mozApps.install("");
     request.onerror = function() {
       ok(request.error.name == "INVALID_URL", "Got expected INVALID_URL");
       continueTest();
@@ -269,17 +268,16 @@ https://bugzilla.mozilla.org/show_bug.cg
   function getAppURL(app) {
     if (!app)
       return gBaseURL + "file_app.sjs?apptype=hosted";
     return app.origin + app.manifest.launch_path;
   }
 
   function finish() {
     SpecialPowers.clearUserPref("dom.mozBrowserFramesEnabled");
-    //SpecialPowers.setAllAppsLaunchable(launchableValue);
     SimpleTest.finish();
   }
 
   function doReload() {
     window.location.reload(true);
   }
 
   </script>
--- a/dom/apps/tests/test_bug_795164.html
+++ b/dom/apps/tests/test_bug_795164.html
@@ -13,40 +13,39 @@ https://bugzilla.mozilla.org/show_bug.cg
   /** Test for Bug 795164 **/
 
   SimpleTest.waitForExplicitFinish();
 
   var url1 = 'http://test1.example.com/tests/dom/apps/tests/file_app.sjs?apptype=hosted&getmanifest=true';
   var url2 = 'http://test2.example.com/tests/dom/apps/tests/file_app.sjs?apptype=hosted&getmanifest=true';
 
   var gGenerator = runTest();
-  var launchableValue = undefined;
 
   function go() {
     SpecialPowers.pushPermissions(
       [{ "type": "webapps-manage", "allow": 1, "context": document }],
       function() { gGenerator.next() });
   }
 
   function continueTest() {
     try {
       gGenerator.next();
     } catch (e if e instanceof StopIteration) {
-      finish();
+      SimpleTest.finish();
     }
   }
 
   function mozAppsError() {
     ok(false, "mozApps error: " + this.error.name);
-    finish();
+    SimpleTest.finish();
   }
 
   function runTest() {
     // Set up.
-    launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.autoConfirmAppInstall(continueTest);
     yield undefined;
 
     // Keeping track of the number of times `mozApps.mgmt.onuninstall` gets triggered
     let uninstallCount = 0;
 
     navigator.mozApps.mgmt.onuninstall = function() {
       uninstallCount++;
@@ -87,21 +86,16 @@ https://bugzilla.mozilla.org/show_bug.cg
       continueTest();
     };
     yield undefined;
 
     is(uninstallCount, 2, "mgmt.onuninstall got triggered only twice");
 
     navigator.mozApps.mgmt.onuninstall = null;
   }
-
-  function finish() {
-    SpecialPowers.setAllAppsLaunchable(launchableValue);
-    SimpleTest.finish();
-  }
   </script>
 </head>
 <body onload="go()">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=795164">Mozilla Bug 795164</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
--- a/dom/apps/tests/test_install_receipts.html
+++ b/dom/apps/tests/test_install_receipts.html
@@ -47,17 +47,17 @@ function finish() {
 function cbError(aError) {
   ok(false, "Error callback invoked " + aError);
   finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 
 function runTest() {
-  launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+  SpecialPowers.setAllAppsLaunchable(true);
 
   SpecialPowers.autoConfirmAppInstall(continueTest);
   yield undefined;
 
   // Test install with three valid receipts
   let valid_receipt1 = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJwcm9kdWN0IjogeyJ1cmwiOiAiaHR0cHM6Ly93d3cubW96aWxsYS5vcmciLCAic3RvcmVkYXRhIjogIjUxNjkzMTQzNTYifSwgInJlaXNzdWUiOiAiaHR0cDovL21vY2hpLnRlc3Q6ODg4OC9yZWlzc3VlLzUxNjkzMTQzNTYiLCAidXNlciI6IHsidHlwZSI6ICJkaXJlY3RlZC1pZGVudGlmaWVyIiwgInZhbHVlIjogIjRmYjM1MTUxLTJiOWItNGJhMi04MjgzLWM0OWQzODE2NDBiZCJ9LCAidmVyaWZ5IjogImh0dHA6Ly9tb2NoaS50ZXN0Ojg4ODgvdmVyaWZ5LzUxNjkzMTQzNTYiLCAiaXNzIjogImh0dHA6Ly9tb2NoaS50ZXN0Ojg4ODgiLCAiaWF0IjogMTMxMzYwMTg4LCAidHlwIjogInB1cmNoYXNlLXJlY2VpcHQiLCAibmJmIjogMTMxMzYwMTg1LCAiZGV0YWlsIjogImh0dHA6Ly9tb2NoaS50ZXN0Ojg4ODgvcmVjZWlwdC81MTY5MzE0MzU2In0.eZpTEnCLUR3iP3rm9WyJOqx1k66mQaAxqcrvX11r5E0';
 
   let valid_receipt2 = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJwcm9kdWN0IjogeyJ1cmwiOiAiaHR0cHM6Ly93d3cubW96aWxsYS5vcmciLCAic3RvcmVkYXRhIjogIjUxNjkzMTQzNTcifSwgInJlaXNzdWUiOiAiaHR0cDovL21vY2hpLnRlc3Q6ODg4OC9yZWlzc3VlLzUxNjkzMTQzNTYiLCAidXNlciI6IHsidHlwZSI6ICJkaXJlY3RlZC1pZGVudGlmaWVyIiwgInZhbHVlIjogIjRmYjM1MTUxLTJiOWItNGJhMi04MjgzLWM0OWQzODE2NDBiZCJ9LCAidmVyaWZ5IjogImh0dHA6Ly9tb2NoaS50ZXN0Ojg4ODgvdmVyaWZ5LzUxNjkzMTQzNTYiLCAiaXNzIjogImh0dHA6Ly9tb2NoaS50ZXN0Ojg4ODgiLCAiaWF0IjogMTMxMzYwMTg4LCAidHlwIjogInB1cmNoYXNlLXJlY2VpcHQiLCAibmJmIjogMTMxMzYwMTg1LCAiZGV0YWlsIjogImh0dHA6Ly9tb2NoaS50ZXN0Ojg4ODgvcmVjZWlwdC81MTY5MzE0MzU2In0.k7tI0PTaMJf0w0keAHJR6couypGY-EtA38q2xOtSv6k';
--- a/dom/apps/tests/test_operator_app_install.js
+++ b/dom/apps/tests/test_operator_app_install.js
@@ -136,17 +136,16 @@ function next() {
   }
 }
 
 function go() {
   next();
 }
 
 function finish() {
-  SpecialPowers.setBoolPref('dom.mozApps.auto_confirm_install', false);
   SimpleTest.finish();
 }
 
 function mozAppsError() {
   ok(false, "mozApps error: " + this.error.name);
   finish();
 }
 
--- a/dom/apps/tests/test_packaged_app_common.js
+++ b/dom/apps/tests/test_packaged_app_common.js
@@ -1,16 +1,15 @@
 /* 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/. */
 
 var PackagedTestHelper = (function PackagedTestHelper() {
   "use strict";
 
-  var launchableValue;
   var steps;
   var index = -1;
   var gSJSPath = "tests/dom/apps/tests/file_packaged_app.sjs";
   var gSJS = "http://test/" + gSJSPath;
   var gAppName = "appname";
   var gApp = null;
   var gInstallOrigin = "http://mochi.test:8888";
   var timeoutID;
@@ -47,17 +46,16 @@ var PackagedTestHelper = (function Packa
   function start() {
     next();
   }
 
   function finish() {
     if (timeoutID) {
       clearTimeout(timeoutID);
     }
-    SpecialPowers.setAllAppsLaunchable(launchableValue);
     SpecialPowers.removePermission("webapps-manage", document);
     SimpleTest.finish();
   }
 
   function mozAppsError() {
     ok(false, "mozApps error: " + this.error.name);
     finish();
   }
@@ -237,13 +235,12 @@ var PackagedTestHelper = (function Packa
     checkAppState: checkAppState,
     checkAppDownloadError: checkAppDownloadError,
     get gSJSPath() { return gSJSPath; },
     set gSJSPath(aValue) { gSJSPath = aValue },
     get gSJS() { return gSJS; },
     get gAppName() { return gAppName;},
     get gApp() { return gApp; },
     set gApp(aValue) { gApp = aValue; },
-    gInstallOrigin: gInstallOrigin,
-    launchableValue: launchableValue
+    gInstallOrigin: gInstallOrigin
   };
 
 })();
--- a/dom/apps/tests/test_packaged_app_install.html
+++ b/dom/apps/tests/test_packaged_app_install.html
@@ -76,18 +76,17 @@ function checkInstalledApp(aMiniManifest
   };
 }
 
 SimpleTest.waitForExplicitFinish();
 
 var steps = [
   function() {
     // Set up
-    PackagedTestHelper.launchableValue =
-      SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.addPermission("webapps-manage", true, document);
     ok(true, "Set up");
     PackagedTestHelper.next();
   },
   function() {
     ok(true, "autoConfirmAppInstall");
     SpecialPowers.autoConfirmAppInstall(PackagedTestHelper.next);
   },
--- a/dom/apps/tests/test_packaged_app_update.html
+++ b/dom/apps/tests/test_packaged_app_update.html
@@ -90,18 +90,17 @@ function updateApp(aExpectedReady, aPrev
                    true);
 
 }
 
 
 var steps = [
   function() {
     // Set up
-    PackagedTestHelper.launchableValue =
-      SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.addPermission("webapps-manage", true, document);
     ok(true, "Set up");
     PackagedTestHelper.next();
   },
   function() {
     ok(true, "autoConfirmAppInstall");
     SpecialPowers.autoConfirmAppInstall(PackagedTestHelper.next);
   },
--- a/dom/apps/tests/test_receipt_operations.html
+++ b/dom/apps/tests/test_receipt_operations.html
@@ -47,17 +47,17 @@ function finish() {
 function cbError(aError) {
   ok(false, "Error callback invoked " + aError);
   finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 
 function runTest() {
-  launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+  SpecialPowers.setAllAppsLaunchable(true);
 
   SpecialPowers.autoConfirmAppInstall(continueTest);
   yield undefined;
 
   var request = navigator.mozApps.install(gManifestURL);
   request.onerror = cbError;
   request.onsuccess = continueTest;
   yield undefined;
--- a/dom/apps/tests/test_signed_pkg_install.html
+++ b/dom/apps/tests/test_signed_pkg_install.html
@@ -22,17 +22,16 @@ https://bugzilla.mozilla.org/show_bug.cg
 <script class="testbody" type="text/javascript">
 
 "use strict";
 
 const Ci = SpecialPowers.Ci;
 const Cc = SpecialPowers.Cc;
 const Cu = SpecialPowers.Cu;
 
-var launchableValue;
 var index = -1;
 var gDebug = false;
 var gApp = null;
 var gAppName = "Simple App";
 var gInstallOrigin = "http://mochi.test:8888/";
 var gSJSPath = "tests/dom/apps/tests/signed_app.sjs";
 var gSJS = gInstallOrigin + gSJSPath;
 var gPackagePath = gInstallOrigin + "tests/dom/apps/tests/";
@@ -90,17 +89,17 @@ var steps = [
     // Set up
     info("Test Initial Setup");
     gSignedAppOriginsStr = SpecialPowers.getCharPref("dom.mozApps.signed_apps_installable_from");
     var signedAppOriginsStr = gSignedAppOriginsStr.concat("," + gInstallOrigin.slice(0, -1));
     SpecialPowers.pushPrefEnv({'set': [['dom.mozApps.signed_apps_installable_from', signedAppOriginsStr]]}, function() {
       var url = SimpleTest.getTestFileURL("chromeAddCert.js");
       var script = SpecialPowers.loadChromeScript(url);
       script.addMessageListener("addCertCompleted", function() {
-        launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+        SpecialPowers.setAllAppsLaunchable(true);
         SpecialPowers.addPermission("webapps-manage", true, document);
         info("Test CA Certificate Selected");
         PackagedTestHelper.next();
         script.destroy();
       });
     });
   },
   function() {
--- a/dom/apps/tests/test_uninstall_errors.html
+++ b/dom/apps/tests/test_uninstall_errors.html
@@ -13,40 +13,39 @@ https://bugzilla.mozilla.org/show_bug.cg
   /** Test for Bug 830258 **/
 
   SimpleTest.waitForExplicitFinish();
 
   var url1 = 'http://test1.example.com/tests/dom/apps/tests/file_app.sjs?apptype=hosted&getmanifest=true';
   var url2 = 'http://test2.example.com/tests/dom/apps/tests/file_app.sjs?apptype=hosted&getmanifest=true';
 
   var gGenerator = runTest();
-  var launchableValue = undefined;
 
   function go() {
     SpecialPowers.pushPermissions(
       [{ "type": "webapps-manage", "allow": 1, "context": document }],
       function() { gGenerator.next() });
   }
 
   function continueTest() {
     try {
       gGenerator.next();
     } catch (e if e instanceof StopIteration) {
-      finish();
+      SimpleTest.finish();
     }
   }
 
   function mozAppsError() {
     ok(false, "mozApps error: " + this.error.name);
-    finish();
+    SimpleTest.finish();
   }
 
   function runTest() {
     // Set up.
-    launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.autoConfirmAppInstall(continueTest);
     yield undefined;
 
     let request = navigator.mozApps.install(url1);
     request.onerror = mozAppsError;
     request.onsuccess = continueTest;
     yield undefined;
     let app1 = request.result;
@@ -87,21 +86,16 @@ https://bugzilla.mozilla.org/show_bug.cg
       continueTest();
     };
     request.onerror = function() {
       ok(false, "Fail to uninstall the app2");
       continueTest();
     };
     yield undefined;
   }
-
-  function finish() {
-    SpecialPowers.setAllAppsLaunchable(launchableValue);
-    SimpleTest.finish();
-  }
   </script>
 </head>
 <body onload="go()">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=830258">Mozilla Bug 830258</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
--- a/dom/browser-element/mochitest/browserElement_AppFramePermission.js
+++ b/dom/browser-element/mochitest/browserElement_AppFramePermission.js
@@ -3,28 +3,17 @@
 
 // Bug 777384 - Test mozapp permission.
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 browserElementTestHelpers.setEnabledPref(true);
 browserElementTestHelpers.addPermission();
 
-function makeAllAppsLaunchable() {
-  var originalValue = SpecialPowers.setAllAppsLaunchable(true);
-
-  // Clean up after ourselves once tests are done so the test page is unloaded.
-  window.addEventListener("unload", function restoreAllAppsLaunchable(event) {
-    if (event.target == window.document) {
-      window.removeEventListener("unload", restoreAllAppsLaunchable, false);
-      SpecialPowers.setAllAppsLaunchable(originalValue);
-    }
-  }, false);
-}
-makeAllAppsLaunchable();
+SpecialPowers.setAllAppsLaunchable(true);
 
 function testAppElement(expectAnApp, callback) {
   var iframe = document.createElement('iframe');
   SpecialPowers.wrap(iframe).mozbrowser = true;
   iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
   iframe.addEventListener('mozbrowsershowmodalprompt', function(e) {
     is(e.detail.message == 'app', expectAnApp, e.detail.message);
     SimpleTest.executeSoon(callback);
--- a/dom/browser-element/mochitest/priority/test_Preallocated.html
+++ b/dom/browser-element/mochitest/priority/test_Preallocated.html
@@ -56,17 +56,17 @@ function runTest()
               "false for this test to work.");
     SimpleTest.finish();
     return;
   }
 
   // Ensure that the preallocated process initially gets BACKGROUND priority.
   // That's it.
   expectProcessCreated().then(function(childID) {
-    return expectPriorityChange(childID, 'PREALLOC');
+    return expectPriorityChange(childID, 'PREALLOC', 'CPU_LOW');
   }).then(function() {
     // We need to set the pref asynchoronously or the preallocated process won't
     // be shut down.
     SimpleTest.executeSoon(function(){
       cleanUp();
       SimpleTest.finish();
     });
   });
--- a/dom/indexedDB/test/webapp_clearBrowserData.js
+++ b/dom/indexedDB/test/webapp_clearBrowserData.js
@@ -116,26 +116,25 @@ function start()
     return;
   }
 
   SpecialPowers.addPermission("browser", true, document);
   SpecialPowers.addPermission("browser", true, { manifestURL: manifestURL,
                                                  isInBrowserElement: false });
   SpecialPowers.addPermission("embed-apps", true, document);
 
-  let originalAllAppsLaunchable = SpecialPowers.setAllAppsLaunchable(true);
+  SpecialPowers.setAllAppsLaunchable(true);
 
   window.addEventListener("unload", function cleanup(event) {
     if (event.target == document) {
       window.removeEventListener("unload", cleanup, false);
 
       SpecialPowers.removePermission("browser", location.href);
       SpecialPowers.removePermission("browser",
                                      location.protocol + "//" + appDomain);
       SpecialPowers.removePermission("embed-apps", location.href);
-      SpecialPowers.setAllAppsLaunchable(originalAllAppsLaunchable);
     }
   }, false);
 
   SpecialPowers.pushPrefEnv({
     "set": [["dom.mozBrowserFramesEnabled", true]]
   }, runTest);
 }
--- a/dom/ipc/PreallocatedProcessManager.cpp
+++ b/dom/ipc/PreallocatedProcessManager.cpp
@@ -55,26 +55,24 @@ public:
   void PublishSpareProcess(ContentParent* aContent);
   void MaybeForgetSpare(ContentParent* aContent);
   void OnNuwaReady();
   bool PreallocatedProcessReady();
   already_AddRefed<ContentParent> GetSpareProcess();
   void RunAfterPreallocatedProcessReady(nsIRunnable* aRunnable);
 
 private:
-  void OnNuwaForkTimeout();
   void NuwaFork();
 
   // initialization off the critical path of app startup.
   CancelableTask* mPreallocateAppProcessTask;
 
   // The array containing the preallocated processes. 4 as the inline storage size
   // should be enough so we don't need to grow the nsAutoTArray.
   nsAutoTArray<nsRefPtr<ContentParent>, 4> mSpareProcesses;
-  nsTArray<CancelableTask*> mNuwaForkWaitTasks;
 
   nsTArray<nsCOMPtr<nsIRunnable> > mDelayedContentParentRequests;
 
   // Nuwa process is ready for creating new process.
   bool mIsNuwaReady;
 #endif
 
 private:
@@ -307,21 +305,16 @@ PreallocatedProcessManagerImpl::PublishS
     AutoJSContext cx;
     nsCOMPtr<nsIMessageBroadcaster> ppmm =
       do_GetService("@mozilla.org/parentprocessmessagemanager;1");
     nsresult rv = ppmm->BroadcastAsyncMessage(
       NS_LITERAL_STRING("TEST-ONLY:nuwa-add-new-process"),
       JS::NullHandleValue, JS::NullHandleValue, cx, 1);
   }
 
-  if (!mNuwaForkWaitTasks.IsEmpty()) {
-    mNuwaForkWaitTasks.ElementAt(0)->Cancel();
-    mNuwaForkWaitTasks.RemoveElementAt(0);
-  }
-
   mSpareProcesses.AppendElement(aContent);
 
   if (!mDelayedContentParentRequests.IsEmpty()) {
     nsCOMPtr<nsIRunnable> runnable = mDelayedContentParentRequests[0];
     mDelayedContentParentRequests.RemoveElementAt(0);
     NS_DispatchToMainThread(runnable);
   }
 }
@@ -370,37 +363,18 @@ PreallocatedProcessManagerImpl::OnNuwaRe
 bool
 PreallocatedProcessManagerImpl::PreallocatedProcessReady()
 {
   return !mSpareProcesses.IsEmpty();
 }
 
 
 void
-PreallocatedProcessManagerImpl::OnNuwaForkTimeout()
-{
-  if (!mNuwaForkWaitTasks.IsEmpty()) {
-    mNuwaForkWaitTasks.RemoveElementAt(0);
-  }
-
-  // We haven't RecvAddNewProcess() after NuwaFork(). Maybe the main
-  // thread of the Nuwa process is in deadlock.
-  MOZ_ASSERT(false, "Can't fork from the nuwa process.");
-}
-
-void
 PreallocatedProcessManagerImpl::NuwaFork()
 {
-  CancelableTask* nuwaForkTimeoutTask = NewRunnableMethod(
-    this, &PreallocatedProcessManagerImpl::OnNuwaForkTimeout);
-  mNuwaForkWaitTasks.AppendElement(nuwaForkTimeoutTask);
-
-  MessageLoop::current()->PostDelayedTask(FROM_HERE,
-                                          nuwaForkTimeoutTask,
-                                          NUWA_FORK_WAIT_DURATION_MS);
   mPreallocatedAppProcess->SendNuwaFork();
 }
 #endif
 
 void
 PreallocatedProcessManagerImpl::Disable()
 {
   if (!mEnabled) {
--- a/dom/ipc/ProcessPriorityManager.cpp
+++ b/dom/ipc/ProcessPriorityManager.cpp
@@ -253,17 +253,17 @@ public:
   void OnAudioChannelProcessChanged(nsISupports* aSubject);
   void OnRemoteBrowserFrameShown(nsISupports* aSubject);
   void OnTabParentDestroyed(nsISupports* aSubject);
   void OnFrameloaderVisibleChanged(nsISupports* aSubject);
   void OnChannelConnected(nsISupports* aSubject);
 
   ProcessPriority CurrentPriority();
   ProcessPriority ComputePriority();
-  ProcessCPUPriority ComputeCPUPriority();
+  ProcessCPUPriority ComputeCPUPriority(ProcessPriority aPriority);
 
   void ScheduleResetPriority(const char* aTimeoutPref);
   void ResetPriority();
   void ResetPriorityNow();
   void ResetCPUPriorityNow();
 
   /**
    * This overload is equivalent to SetPriorityNow(aPriority,
@@ -951,23 +951,23 @@ ParticularProcessPriorityManager::Comput
   }
 
   return HasAppType("homescreen") ?
          PROCESS_PRIORITY_BACKGROUND_HOMESCREEN :
          PROCESS_PRIORITY_BACKGROUND;
 }
 
 ProcessCPUPriority
-ParticularProcessPriorityManager::ComputeCPUPriority()
+ParticularProcessPriorityManager::ComputeCPUPriority(ProcessPriority aPriority)
 {
-  if (mPriority == PROCESS_PRIORITY_PREALLOC) {
+  if (aPriority == PROCESS_PRIORITY_PREALLOC) {
     return PROCESS_CPU_PRIORITY_LOW;
   }
 
-  if (mPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH) {
+  if (aPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH) {
     return PROCESS_CPU_PRIORITY_NORMAL;
   }
 
   return ProcessPriorityManagerImpl::GetSingleton()->
     OtherProcessHasHighPriority(this) ?
     PROCESS_CPU_PRIORITY_LOW :
     PROCESS_CPU_PRIORITY_NORMAL;
 }
@@ -977,17 +977,17 @@ ParticularProcessPriorityManager::ResetC
 {
   SetPriorityNow(mPriority);
 }
 
 void
 ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority,
                                                  uint32_t aBackgroundLRU)
 {
-  SetPriorityNow(aPriority, ComputeCPUPriority(), aBackgroundLRU);
+  SetPriorityNow(aPriority, ComputeCPUPriority(aPriority), aBackgroundLRU);
 }
 
 void
 ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority,
                                                  ProcessCPUPriority aCPUPriority,
                                                  uint32_t aBackgroundLRU)
 {
   if (aPriority == PROCESS_PRIORITY_UNKNOWN) {
--- a/dom/ipc/tests/mochitest.ini
+++ b/dom/ipc/tests/mochitest.ini
@@ -1,3 +1,5 @@
 [test_NuwaProcessCreation.html]
 run-if = toolkit == 'gonk'
+[test_NuwaProcessDeadlock.html]
+run-if = toolkit == 'gonk'
 [test_child_docshell.html]
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/test_NuwaProcessDeadlock.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test if Nuwa process created successfully.
+-->
+<head>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript;version=1.7">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+function TestLoader() {}
+
+TestLoader.prototype = {
+  _waitingTask: 0,
+  onTestReady: null,
+  unlockTestReady: function() {
+    this._waitingTask--;
+    this._maybeLoadTest();
+  },
+  lockTestReady: function() {
+    this._waitingTask++;
+  },
+  _maybeLoadTest: function() {
+    if (this._waitingTask == 0) {
+      this.onTestReady();
+    }
+  }
+}
+
+var testLoader = new TestLoader();
+testLoader.lockTestReady();
+window.addEventListener('load', function() {
+  testLoader.unlockTestReady();
+});
+
+function setPref(pref, value) {
+  testLoader.lockTestReady();
+  if (value !== undefined && value !== null) {
+    SpecialPowers.pushPrefEnv({'set': [[pref, value]]}, function() { testLoader.unlockTestReady(); });
+  } else {
+    SpecialPowers.pushPrefEnv({'clear': [[pref]]}, function() { testLoader.unlockTestReady(); });
+  }
+}
+
+setPref('dom.ipc.processPriorityManager.testMode', true);
+setPref('dom.ipc.processPriorityManager.enabled', true);
+setPref('dom.ipc.processPriorityManager.backgroundLRUPoolLevels', 2);
+setPref('dom.ipc.processPrelaunch.testMode', true);  // For testing deadlock.
+
+function runTest()
+{
+  // Shutdown preallocated process.
+  SpecialPowers.setBoolPref('dom.ipc.processPrelaunch.enabled', false);
+  let cpmm = SpecialPowers.Cc["@mozilla.org/childprocessmessagemanager;1"]
+                          .getService(SpecialPowers.Ci.nsISyncMessageSender);
+  let seenNuwaReady = false;
+  let msgHandler = {
+    receiveMessage: function receiveMessage(msg) {
+      msg = SpecialPowers.wrap(msg);
+      if (msg.name == 'TEST-ONLY:nuwa-ready') {
+        ok(true, "Got nuwa-ready");
+        is(seenNuwaReady, false, "Already received nuwa ready");
+        seenNuwaReady = true;
+      } else if (msg.name == 'TEST-ONLY:nuwa-add-new-process') {
+        ok(true, "Got nuwa-add-new-process");
+        is(seenNuwaReady, true, "Receive nuwa-add-new-process before nuwa-ready");
+        testEnd();
+      }
+    }
+  };
+  let timeout = setTimeout(function() {
+    ok(false, "Nuwa process is not launched");
+    testEnd();
+  }, 90000);
+
+  function testEnd() {
+    cpmm.removeMessageListener("TEST-ONLY:nuwa-ready", msgHandler);
+    cpmm.removeMessageListener("TEST-ONLY:nuwa-add-new-process", msgHandler);
+    clearTimeout(timeout);
+    setPref('dom.ipc.processPrelaunch.testMode', false);
+    SimpleTest.finish();
+  }
+
+  cpmm.addMessageListener("TEST-ONLY:nuwa-ready", msgHandler);
+  cpmm.addMessageListener("TEST-ONLY:nuwa-add-new-process", msgHandler);
+
+
+  // Setting this pref to true should cause us to prelaunch a process.
+  SpecialPowers.setBoolPref('dom.ipc.processPrelaunch.enabled', true);
+}
+
+testLoader.onTestReady = runTest;
+</script>
+</body>
+</html>
--- a/dom/smil/test/smilAnimateMotionValueLists.js
+++ b/dom/smil/test/smilAnimateMotionValueLists.js
@@ -103,18 +103,21 @@ const gValidKeyPoints = [
   "0; 0; 1",
   "0; 1; 1",
   "0; 0; 1;", // Trailing semicolons are allowed
   "0; 0; 1; ",
   "0; 0.000; 1",
   "0; 0.000001; 1",
 ];
 
+// Should have 3 values to be valid.
+// Same as number of keyTimes values
 const gInvalidKeyPoints = [
   "0; 1",
+  "0; 0.5; 0.75; 1",
   "0; 1;",
   "0",
   "1",
   "a",
   "",
   "  ",
   "0; -0.1; 1",
   "0; 1.1; 1",
--- a/dom/smil/test/smilTestUtils.js
+++ b/dom/smil/test/smilTestUtils.js
@@ -14,32 +14,16 @@ const MPATH_TARGET_ID = "smilTestUtilsTe
 function extend(child, supertype)
 {
    child.prototype.__proto__ = supertype.prototype;
 }
 
 // General Utility Methods
 var SMILUtil =
 {
-  // Returns true if SMIL is enabled, false otherwise
-  // XXXdholbert There should be a "nicer" way to do this - right now this will
-  // trigger a 'NotYetImplemented' assertion on STDOUT, if SMIL is disabled.
-  isSMILEnabled : function()
-  {
-    var svg = SMILUtil.getSVGRoot();
-    try {
-      SMILUtil.getSVGRoot().animationsPaused();
-    } catch(e) {
-      // Exception --> SMIL disabled
-      return false;
-    }
-    // No exceptions --> SMIL enabled
-    return true;
-  },
-
   // Returns the first matched <svg> node in the document
   getSVGRoot : function()
   {
     return SMILUtil.getFirstElemWithTag("svg");
   },
 
   // Returns the first element in the document with the matching tag
   getFirstElemWithTag : function(aTargetTag)
--- a/dom/smil/test/test_smilAnimateMotion.xhtml
+++ b/dom/smil/test/test_smilAnimateMotion.xhtml
@@ -27,22 +27,16 @@ https://bugzilla.mozilla.org/show_bug.cg
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   var timingData = new SMILTimingData(1.0, 6.0);
   testBundleList(gMotionBundles, timingData);
 
--- a/dom/smil/test/test_smilAnimateMotionInvalidValues.xhtml
+++ b/dom/smil/test/test_smilAnimateMotionInvalidValues.xhtml
@@ -41,17 +41,17 @@ function createAnim()
   return gRect.appendChild(anim);
 }
 
 function removeElem(aElem)
 {
   aElem.parentNode.removeChild(aElem);
 }
 
-function testAttr(aAttrName, aAttrValueArray, aIsValid, aIsTodo)
+function testAttr(aAttrName, aAttrValueArray, aIsValid)
 {
   var componentsToCheck;
 
   for (var i in aAttrValueArray) {
     var curVal = aAttrValueArray[i];
     var anim = createAnim();
     anim.setAttribute(aAttrName, curVal);
     if (aAttrName == "rotate") {
@@ -73,22 +73,22 @@ function testAttr(aAttrName, aAttrValueA
       }
     }
 
     var curCTM = gRect.getCTM();
     if (aIsValid) {
       var errMsg = "CTM should have changed when applying animateMotion " +
         "with '" + aAttrName + "' set to valid value '" + curVal + "'";
       CTMUtil.assertCTMNotEqual(curCTM, gUnAnimatedCTM, componentsToCheck,
-                                errMsg, aIsTodo);
+                                errMsg, false);
     } else {
       var errMsg = "CTM should not have changed when applying animateMotion " +
         "with '" + aAttrName + "' set to invalid value '" + curVal + "'";
       CTMUtil.assertCTMEqual(curCTM, gUnAnimatedCTM, componentsToCheck,
-                             errMsg, aIsTodo);
+                             errMsg, false);
     }
     removeElem(anim);
   }
 }
 
 function createPath(aPathDescription)
 {
   var path = document.createElementNS(SVGNS, "path");
@@ -99,17 +99,17 @@ function createPath(aPathDescription)
 
 function createMpath(aAnimElement)
 {
   var mpath = document.createElementNS(SVGNS, "mpath");
   mpath.setAttributeNS(XLINKNS, "href", "#thePath");
   return aAnimElement.appendChild(mpath);
 }
 
-function testMpathElem(aPathValueArray, aIsValid, aIsTodo)
+function testMpathElem(aPathValueArray, aIsValid)
 {
   for (var i in aPathValueArray) {
     var curVal = aPathValueArray[i];
     var anim = createAnim();
     var mpath = createMpath(anim);
     var path = createPath(curVal);
 
     // Apply a supplementary rotation to make sure that we don't apply it if
@@ -117,64 +117,58 @@ function testMpathElem(aPathValueArray, 
     anim.setAttribute("rotate", Math.PI/4);
     componentsToCheck = CTMUtil.CTM_COMPONENTS_ALL;
 
     if (aIsValid) {
       var errMsg = "CTM should have changed when applying animateMotion " +
         "with mpath linking to a path with valid value '" + curVal + "'";
 
       CTMUtil.assertCTMNotEqual(gRect.getCTM(), gUnAnimatedCTM,
-                                componentsToCheck, errMsg, aIsTodo);
+                                componentsToCheck, errMsg, false);
     } else {
       var errMsg = "CTM should not have changed when applying animateMotion " +
         "with mpath linking to a path with invalid value '" + curVal + "'";
       CTMUtil.assertCTMEqual(gRect.getCTM(), gUnAnimatedCTM,
-                             componentsToCheck, errMsg, aIsTodo);
+                             componentsToCheck, errMsg, false);
     }
     removeElem(anim);
     removeElem(path);
     removeElem(mpath);
  } 
 }
 
 // Main Function
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
-  testAttr("values", gValidValues, true, false);
-  testAttr("values", gInvalidValues, false, false);
+  testAttr("values", gValidValues, true);
+  testAttr("values", gInvalidValues, false);
 
-  testAttr("rotate", gValidRotate, true, false);
-  testAttr("rotate", gInvalidRotate, false, false);
+  testAttr("rotate", gValidRotate, true);
+  testAttr("rotate", gInvalidRotate, false);
 
-  testAttr("to", gValidToBy, true, false);
-  testAttr("to", gInvalidToBy, false, false);
+  testAttr("to", gValidToBy, true);
+  testAttr("to", gInvalidToBy, false);
 
-  testAttr("by", gValidToBy, true, false);
-  testAttr("by", gInvalidToBy, false, false);
+  testAttr("by", gValidToBy, true);
+  testAttr("by", gInvalidToBy, false);
 
-  testAttr("path", gValidPath, true, false);
-  testAttr("path", gInvalidPath, false, false);
-  testAttr("path", gValidPathWithErrors, true, false);
+  testAttr("path", gValidPath, true);
+  testAttr("path", gInvalidPath, false);
+  testAttr("path", gValidPathWithErrors, true);
 
-  testAttr("keyPoints", gValidKeyPoints, true, false);
-  testAttr("keyPoints", gInvalidKeyPoints, false, false);
+  testAttr("keyPoints", gValidKeyPoints, true);
+  testAttr("keyPoints", gInvalidKeyPoints, false);
 
-  testMpathElem(gValidPath, true, false);
-  testMpathElem(gInvalidPath, false, false);
+  testMpathElem(gValidPath, true);
+  testMpathElem(gInvalidPath, false);
 
   SimpleTest.finish();
 }
 
 window.addEventListener("load", main, false);
 ]]>
 </script>
 </pre>
--- a/dom/smil/test/test_smilAnimateMotionOverrideRules.xhtml
+++ b/dom/smil/test/test_smilAnimateMotionOverrideRules.xhtml
@@ -193,22 +193,16 @@ function testAttrSettings(aAttrValueHash
   // CLEAN UP
   SMILUtil.getSVGRoot().setCurrentTime(0);
   removeElem(animElement);
 }
 
 // Main Function
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   runTest();
   SimpleTest.finish();
 }
--- a/dom/smil/test/test_smilCSSFontStretchRelative.xhtml
+++ b/dom/smil/test/test_smilCSSFontStretchRelative.xhtml
@@ -75,22 +75,16 @@ function testFontStretchValue(baseValue,
 
   // Removing animation should clear animated effects
   textElem.removeChild(animElem);
   svg.removeChild(gElem);
 }
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   var valuesList = gFontStretchValues;
   for (var baseIdx in valuesList) {
     // 'narrower' and 'wider' are expected to shift us by one slot, but not
     // past the ends of the list of possible values.
     var narrowerIdx = Math.max(baseIdx - 1, 0);
     var widerIdx =    Math.min(baseIdx + 1, valuesList.length - 1);
 
     testFontStretchValue(valuesList[baseIdx],
--- a/dom/smil/test/test_smilCSSFromBy.xhtml
+++ b/dom/smil/test/test_smilCSSFromBy.xhtml
@@ -26,22 +26,16 @@
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   testBundleList(gFromByBundles, new SMILTimingData(1.0, 1.0));
 
   // Set "display:none" on everything and run the tests again
--- a/dom/smil/test/test_smilCSSFromTo.xhtml
+++ b/dom/smil/test/test_smilCSSFromTo.xhtml
@@ -49,22 +49,16 @@ function checkForUntestedProperties(bund
   // Warn about remaining (untested) properties
   for (var untestedProp in propertySet) {
     ok(false, "No tests for property '" + untestedProp + "'");
   }
 }
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   // FIRST: Warn about any properties that are missing tests
   checkForUntestedProperties(gFromToBundles);
 
--- a/dom/smil/test/test_smilCSSInherit.xhtml
+++ b/dom/smil/test/test_smilCSSInherit.xhtml
@@ -51,22 +51,16 @@
 </svg>
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 SimpleTest.waitForExplicitFinish();
 
 function main() {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Pause & seek to halfway through animation
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
   svg.setCurrentTime(0.5);
 
   var text = document.getElementsByTagName("text")[0];
   var computedVal = SMILUtil.getComputedStyleSimple(text, "font-size");
--- a/dom/smil/test/test_smilCSSInvalidValues.xhtml
+++ b/dom/smil/test/test_smilCSSInvalidValues.xhtml
@@ -35,22 +35,16 @@ var invalidTestcaseBundles = [
     new AnimTestcaseFromTo("greeeen", "red",  { noEffect: true }),
     new AnimTestcaseFromTo("rgb(red, 255, 255)", "red", { noEffect: true }),
     new AnimTestcaseFromTo("#FFFFFFF", "red", { noEffect: true }),
     new AnimTestcaseFromTo("bogus", "bogus",  { noEffect: true }),
   ]),
 ];
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   // Run the tests
   testBundleList(invalidTestcaseBundles, new SMILTimingData(1.0, 1.0));
 
--- a/dom/smil/test/test_smilCSSPaced.xhtml
+++ b/dom/smil/test/test_smilCSSPaced.xhtml
@@ -21,22 +21,16 @@
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   testBundleList(gPacedBundles, new SMILTimingData(1.0, 6.0));
   // Set "display:none" on everything and run the tests again
   SMILUtil.hideSubtree(SMILUtil.getSVGRoot(), false, false);
--- a/dom/smil/test/test_smilDynamicDelayedBeginElement.xhtml
+++ b/dom/smil/test/test_smilDynamicDelayedBeginElement.xhtml
@@ -45,22 +45,16 @@ function createAnim() {
   a.setAttribute('begin', 'indefinite');
   a.setAttribute('dur', '3s');
   a.setAttribute('fill', 'freeze');
   return a;
 }
 
 // Main Functions
 function main() {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // In unpatched Firefox builds, we'll only trigger Bug 699143 if we insert
   // an animation and call beginElement() **after** the document start-time.
   // Hence, we use executeSoon here to allow some time to pass.  (And then
   // we'll use a short busy-loop, for good measure.)
   SimpleTest.executeSoon(runTest);
 }
 
 function runTest() {
--- a/dom/smil/test/test_smilMappedAttrFromBy.xhtml
+++ b/dom/smil/test/test_smilMappedAttrFromBy.xhtml
@@ -27,22 +27,16 @@
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   var testBundles = convertCSSBundlesToMappedAttr(gFromByBundles);
   testBundleList(testBundles, new SMILTimingData(1.0, 1.0));
 
--- a/dom/smil/test/test_smilMappedAttrFromTo.xhtml
+++ b/dom/smil/test/test_smilMappedAttrFromTo.xhtml
@@ -50,22 +50,16 @@ function checkForUntestedAttributes(bund
   // Warn about remaining (untested) properties
   for (var untestedProp in attributeSet) {
     ok(false, "No tests for attribute '" + untestedProp + "'");
   }
 }
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   var testBundles = convertCSSBundlesToMappedAttr(gFromToBundles);
 
   // FIRST: Warn about any attributes that are missing tests
--- a/dom/smil/test/test_smilMappedAttrPaced.xhtml
+++ b/dom/smil/test/test_smilMappedAttrPaced.xhtml
@@ -22,22 +22,16 @@
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   var testBundles = convertCSSBundlesToMappedAttr(gPacedBundles);
   testBundleList(testBundles, new SMILTimingData(1.0, 6.0));
 
--- a/dom/smil/test/test_smilTextZoom.xhtml
+++ b/dom/smil/test/test_smilTextZoom.xhtml
@@ -30,22 +30,16 @@ SimpleTest.waitForExplicitFinish();
 function verifyStyle(aNode, aPropertyName, aExpectedVal)
 {
   var computedVal = SMILUtil.getComputedStyleSimple(aNode, aPropertyName);
   is(computedVal, aExpectedVal, "computed value of " + aPropertyName);
 }
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out pause
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   // Set text zoom to 2x
   var origTextZoom =  SpecialPowers.getTextZoom(window);
   SpecialPowers.setTextZoom(window, 2);
--- a/dom/tests/mochitest/beacon/test_beaconContentPolicy.html
+++ b/dom/tests/mochitest/beacon/test_beaconContentPolicy.html
@@ -25,73 +25,84 @@ const Ci = SpecialPowers.Ci;
 // not enabled by default yet.
 SimpleTest.waitForExplicitFinish();
 
 var policy = setupPolicy();
 
 SpecialPowers.pushPrefEnv({'set': [["beacon.enabled", true]]}, beginTest);
 
 function setupPolicy() {
+  info("creating the policy");
   var policyID = SpecialPowers.wrap(SpecialPowers.Components).ID("{b80e19d0-878f-d41b-2654-194714a4115c}");
   var policyName = "@mozilla.org/testpolicy;1";
   var policy = {
     // nsISupports implementation
     QueryInterface: function(iid) {
+      info("QueryInterface called " + iid);
       iid = SpecialPowers.wrap(iid);
       if (iid.equals(Ci.nsISupports) ||
         iid.equals(Ci.nsIFactory) ||
         iid.equals(Ci.nsIContentPolicy))
         return this;
+      info("unknown interface!");
       throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
     },
     
     // nsIFactory implementation
     createInstance: function(outer, iid) {
+      info("createInstance called " + iid);
       return this.QueryInterface(iid);
     },
 
     // nsIContentPolicy implementation
     shouldLoad: function(contentType, contentLocation, requestOrigin, context, mimeTypeGuess, extra) {
+      info("shouldLoad");
+      info("url: " + SpecialPowers.wrap(contentLocation).spec);
       // Remember last content type seen for the test url
 
       if (SpecialPowers.wrap(contentLocation).spec == beaconUrl) {
         is(contentType,  Ci.nsIContentPolicy.TYPE_BEACON, "Beacon content type should match expected.  is: " + contentType + " should be: " + Ci.nsIContentPolicy.TYPE_BEACON);
         teardownPolicy();
         SimpleTest.finish();
       }
 
       return Ci.nsIContentPolicy.ACCEPT;
     },
 
     shouldProcess: function(contentType, contentLocation, requestOrigin, context, mimeTypeGuess, extra) {
+      info("shouldProcess");
+      info("url: " + SpecialPowers.wrap(contentLocation).spec);
       return Ci.nsIContentPolicy.ACCEPT;
     }
   }
   policy = SpecialPowers.wrapCallbackObject(policy);
 
   // Register content policy
+  info("registering the policy");
   var componentManager = SpecialPowers.wrap(SpecialPowers.Components).manager.QueryInterface(Ci.nsIComponentRegistrar);
   componentManager.registerFactory(policyID, "Test content policy", policyName, policy);
 
   var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
   categoryManager.addCategoryEntry("content-policy", policyName, policyName, false, true);
 
+  info("returning the policy");
   return { 'policy': policy, 'policyID': policyID, 'policyName': policyName };
 }
 
 function teardownPolicy() {
   setTimeout(function() {
     // policy will not be removed from the category correctly
     var componentManager = SpecialPowers.wrap(SpecialPowers.Components).manager.QueryInterface(Ci.nsIComponentRegistrar);
     componentManager.unregisterFactory(policy.policyID, policy.policy);
     var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
     categoryManager.deleteCategoryEntry("content-policy", policy.policyName, false);
   }, 0);
 }
 
 function beginTest() {
+  info("sending the beacon");
   navigator.sendBeacon(beaconUrl, "bacon would have been a better name than beacon");
 }
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/tests/mochitest/localstorage/test_app_uninstall.html
+++ b/dom/tests/mochitest/localstorage/test_app_uninstall.html
@@ -44,93 +44,63 @@ const Cu = Components.utils;
 SimpleTest.waitForExplicitFinish();
 
 var permManager = Cc["@mozilla.org/permissionmanager;1"]
                     .getService(Ci.nsIPermissionManager);
 var appsService = Cc['@mozilla.org/AppsService;1']
                     .getService(Ci.nsIAppsService);
 
 /**
- * This function will make sure that the next applications we try to install
- * will be installed. That means it will behave like if the user allowed the app
- * to be installed in the door hanger.
- */
-function confirmNextInstall() {
-  var panel = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIWebNavigation)
-                        .QueryInterface(Ci.nsIDocShell)
-                        .chromeEventHandler.ownerDocument.defaultView
-                        .PopupNotifications.panel
-
-  panel.addEventListener("popupshown", function() {
-    panel.removeEventListener("popupshown", arguments.callee, false);
-    this.childNodes[0].button.doCommand();
-  }, false);
-}
-
-/**
  * Initialize the |storage| that has been given with "foo" => "bar".
  * Checks that the storage wasn't initialized and checks that the initialization
  * was successful.
  */
 function setupStorage(storage) {
   is(storage.getItem("foo"), null, "no data");
 
   storage.setItem("foo", "bar");
   is(storage.getItem("foo"), "bar", "data written");
 }
 
 permManager.addFromPrincipal(window.document.nodePrincipal, "webapps-manage",
                              Ci.nsIPermissionManager.ALLOW_ACTION);
 permManager.addFromPrincipal(window.document.nodePrincipal, "browser",
                              Ci.nsIPermissionManager.ALLOW_ACTION);
 
-var previousPrefs = {
-  mozBrowserFramesEnabled: null,
-  installerDryRun: null,
-};
+SimpleTest.registerCleanupFunction(() => {
+  gWitnessStorage.localStorage.clear();
+  gWitnessStorage.sessionStorage.clear();
 
-// Save the prefs we want to change (so we can re-set them later) and set them
-// to the needed value.
-try {
-  previousPrefs.mozBrowserFramesEnabled = SpecialPowers.getBoolPref('dom.mozBrowserFramesEnabled');
-} catch(e)
-{
-}
-SpecialPowers.setBoolPref('dom.mozBrowserFramesEnabled', true);
-
-try {
-  previousPrefs.installerDryRun = SpecialPowers.getBoolPref('browser.mozApps.installer.dry_run');
-} catch(e) {
-}
-SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', true);
+  permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
+                                  Ci.nsIPermissionManager.ALLOW_ACTION);
+  permManager.removeFromPrincipal(window.document.nodePrincipal, "browser",
+                                  Ci.nsIPermissionManager.ALLOW_ACTION);
+});
 
 // URL of the manifest of the app we want to install.
 const gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
 // ID of the installed app.
 var gTestAppId = 0;
 // Cookies currently in the system.
 var gCurrentCookiesCount = 0;
 // Storages from a non-app to make sure we do not remove cookies from everywhere.
 var gWitnessStorage = {};
 // Storages for the app.
 var gAppStorage = {};
 // Storage for a mozbrowser inside the app.
 var gBrowserStorage = {};
 
-addLoadEvent(function() {
+function runTest() {
   /*
    * We are setuping the witness storage (non-app) and will install the
    * application.
    * When the application is installed, we will insert it in an iframe and wait
    * for the load event. to be fired.
    */
 
-  confirmNextInstall();
-
   gWitnessStorage.localStorage = window.frames[0].localStorage;
   gWitnessStorage.sessionStorage = window.frames[0].sessionStorage;
 
   setupStorage(gWitnessStorage.localStorage);
   setupStorage(gWitnessStorage.sessionStorage);
 
   navigator.mozApps.install(gManifestURL, null).onsuccess = function() {
     gTestAppId = appsService.getAppLocalIdByManifestURL(gManifestURL);
@@ -140,17 +110,17 @@ addLoadEvent(function() {
     frame.setAttribute('mozapp', gManifestURL);
     frame.src = 'http://example.com/tests/error404';
     frame.name = 'app';
 
     frame.addEventListener('load', appFrameLoadEvent);
 
     document.body.appendChild(frame);
   };
-});
+}
 
 function appFrameLoadEvent() {
   /*
    * The app frame has been loaded. We can now add permissions for the app to
    * create browsers and we will load a page in this browser and wait for the
    * load event.
    */
   permManager.addFromPrincipal(window.frames[1].document.nodePrincipal, "browser",
@@ -197,40 +167,27 @@ function browserLoadEvent() {
           is(gBrowserStorage.localStorage.getItem("foo"), null, "localstorage data have been deleted");
 
           is(gAppStorage.sessionStorage.getItem("foo"), "bar", "sessionstorage data have not been deleted");
           is(gBrowserStorage.sessionStorage.getItem("foo"), "bar", "sessionstorage data have not been deleted");
 
           is(gWitnessStorage.localStorage.getItem("foo"), "bar", "data are still there");
           is(gWitnessStorage.sessionStorage.getItem("foo"), "bar", "data are still there");
 
-          finish();
+          SimpleTest.finish();
 
           return;
         };
       }
     }
   };
 }
 
-/**
- * This method will be called when the test will be done. It is going to clear
- * all storage data, permissions, etc.
- */
-function finish() {
-  gWitnessStorage.localStorage.clear();
-  gWitnessStorage.sessionStorage.clear();
-
-  permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
-                                  Ci.nsIPermissionManager.ALLOW_ACTION);
-  permManager.removeFromPrincipal(window.document.nodePrincipal, "browser",
-                                  Ci.nsIPermissionManager.ALLOW_ACTION);
-
-  SpecialPowers.setBoolPref('dom.mozBrowserFramesEnabled', previousPrefs.mozBrowserFramesEnabled);
-  SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', previousPrefs.installerDryRun);
-
-  SimpleTest.finish();
-}
+addLoadEvent(() =>
+  SpecialPowers.pushPrefEnv({set: [['dom.mozBrowserFramesEnabled', true]]}, () =>
+    SpecialPowers.autoConfirmAppInstall(runTest)
+  )
+);
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/tests/mochitest/localstorage/test_clear_browser_data.html
+++ b/dom/tests/mochitest/localstorage/test_clear_browser_data.html
@@ -44,37 +44,16 @@ const Cu = Components.utils;
 
 SimpleTest.waitForExplicitFinish();
 
 var permManager = Cc["@mozilla.org/permissionmanager;1"]
                     .getService(Ci.nsIPermissionManager);
 var appsService = Cc['@mozilla.org/AppsService;1']
                     .getService(Ci.nsIAppsService);
 
-var Webapps = {};
-Cu.import("resource://gre/modules/Webapps.jsm", Webapps);
-
-/**
- * This function will make sure that the next applications we try to install
- * will be installed. That means it will behave like if the user allowed the app
- * to be installed in the door hanger.
- */
-function confirmNextInstall() {
-  var panel = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIWebNavigation)
-                        .QueryInterface(Ci.nsIDocShell)
-                        .chromeEventHandler.ownerDocument.defaultView
-                        .PopupNotifications.panel
-
-  panel.addEventListener("popupshown", function() {
-    panel.removeEventListener("popupshown", arguments.callee, false);
-    this.childNodes[0].button.doCommand();
-  }, false);
-}
-
 /**
  * Initialize the |storage| that has been given with "foo" => "bar".
  * Checks that the storage wasn't initialized and checks that the initialization
  * was successful.
  */
 function setupStorage(storage) {
   is(storage.getItem("foo"), null, "no data");
 
@@ -82,63 +61,50 @@ function setupStorage(storage) {
   is(storage.getItem("foo"), "bar", "data written");
 }
 
 permManager.addFromPrincipal(window.document.nodePrincipal, "webapps-manage",
                              Ci.nsIPermissionManager.ALLOW_ACTION);
 permManager.addFromPrincipal(window.document.nodePrincipal, "browser",
                              Ci.nsIPermissionManager.ALLOW_ACTION);
 
-var previousPrefs = {
-  mozBrowserFramesEnabled: null,
-  installerDryRun: null,
-};
+SimpleTest.registerCleanupFunction(() => {
+  gWitnessStorage.localStorage.clear();
+  gWitnessStorage.sessionStorage.clear();
 
-// Save the prefs we want to change (so we can re-set them later) and set them
-// to the needed value.
-try {
-  previousPrefs.mozBrowserFramesEnabled = SpecialPowers.getBoolPref('dom.mozBrowserFramesEnabled');
-} catch(e)
-{
-}
-SpecialPowers.setBoolPref('dom.mozBrowserFramesEnabled', true);
-
-try {
-  previousPrefs.installerDryRun = SpecialPowers.getBoolPref('browser.mozApps.installer.dry_run');
-} catch(e) {
-}
-SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', true);
+  permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
+                                  Ci.nsIPermissionManager.ALLOW_ACTION);
+  permManager.removeFromPrincipal(window.document.nodePrincipal, "browser",
+                                  Ci.nsIPermissionManager.ALLOW_ACTION);
+});
 
 // We want to simulate that all apps are launchable, for testing purpose.
-var gPreviousLaunchableValue = Webapps.DOMApplicationRegistry.allAppsLaunchable;
-Webapps.DOMApplicationRegistry.allAppsLaunchable = true;
+SpecialPowers.setAllAppsLaunchable(true);
 
 // URL of the manifest of the app we want to install.
 const gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
 // ID of the installed app.
 var gTestAppId = 0;
 // Cookies currently in the system.
 var gCurrentCookiesCount = 0;
 // Storages from a non-app to make sure we do not remove cookies from everywhere.
 var gWitnessStorage = {};
 // Storages for the app.
 var gAppStorage = {};
 // Storage for a mozbrowser inside the app.
 var gBrowserStorage = {};
 
-addLoadEvent(function() {
+function runTest() {
   /*
    * We are setuping the witness storage (non-app) and will install the
    * application.
    * When the application is installed, we will insert it in an iframe and wait
    * for the load event. to be fired.
    */
 
-  confirmNextInstall();
-
   gWitnessStorage.localStorage = window.frames[0].localStorage;
   gWitnessStorage.sessionStorage = window.frames[0].sessionStorage;
 
   setupStorage(gWitnessStorage.localStorage);
   setupStorage(gWitnessStorage.sessionStorage);
 
   navigator.mozApps.install(gManifestURL, null).onsuccess = function() {
     gTestAppId = appsService.getAppLocalIdByManifestURL(gManifestURL);
@@ -148,17 +114,17 @@ addLoadEvent(function() {
     frame.setAttribute('mozapp', gManifestURL);
     frame.src = 'http://www.example.com/chrome/dom/tests/mochitest/localstorage/frame_clear_browser_data.html';
     frame.name = 'app';
 
     frame.addEventListener('load', appFrameLoadEvent);
 
     document.body.appendChild(frame);
   };
-});
+}
 
 function appFrameLoadEvent() {
   /*
    * The app frame has been loaded. We can now add permissions for the app to
    * create browsers and we will load a page in this browser and wait for the
    * load event.
    */
   permManager.addFromPrincipal(window.frames[1].document.nodePrincipal, "browser",
@@ -214,42 +180,27 @@ function checks() {
         is(gBrowserStorage.sessionStorage.getItem("foo"), "bar", "sessionstorage data have not been deleted");
 
         is(gAppStorage.localStorage.getItem("foo"), "bar", "data are still there");
         is(gAppStorage.sessionStorage.getItem("foo"), "bar", "data are still there");
 
         is(gWitnessStorage.localStorage.getItem("foo"), "bar", "data are still there");
         is(gWitnessStorage.sessionStorage.getItem("foo"), "bar", "data are still there");
 
-        Webapps.DOMApplicationRegistry.allAppsLaunchable = gPreviousLaunchableValue;
-
         // Now we uninstall the app and make sure everything is clean.
         navigator.mozApps.mgmt.uninstall(app).onsuccess = function() {
-          finish();
+          SimpleTest.finish();
         };
       }
     }
   };
 }
 
-/**
- * This method will be called when the test will be done. It is going to clear
- * all storage data, permissions, etc.
- */
-function finish() {
-  gWitnessStorage.localStorage.clear();
-  gWitnessStorage.sessionStorage.clear();
-
-  permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
-                                  Ci.nsIPermissionManager.ALLOW_ACTION);
-  permManager.removeFromPrincipal(window.document.nodePrincipal, "browser",
-                                  Ci.nsIPermissionManager.ALLOW_ACTION);
-
-  SpecialPowers.setBoolPref('dom.mozBrowserFramesEnabled', previousPrefs.mozBrowserFramesEnabled);
-  SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', previousPrefs.installerDryRun);
-
-  SimpleTest.finish();
-}
+addLoadEvent(() =>
+  SpecialPowers.pushPrefEnv({set: [['dom.mozBrowserFramesEnabled', true]]}, () =>
+    SpecialPowers.autoConfirmAppInstall(runTest)
+  )
+);
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/tests/mochitest/webapps/file_bug_779982.js
+++ b/dom/tests/mochitest/webapps/file_bug_779982.js
@@ -1,10 +1,10 @@
 SimpleTest.waitForExplicitFinish();
-var originalAllAppsLaunchable = SpecialPowers.setAllAppsLaunchable(true);
+SpecialPowers.setAllAppsLaunchable(true);
 
 var fileTestOnCurrentOrigin = 'http://example.org/tests/dom/tests/mochitest/webapps/file_bug_779982.html';
 
 var gData = [
   // APP 1
   {
     app: 'http://example.org/manifest.webapp',
     action: 'getSelf',
@@ -63,17 +63,16 @@ function runTest() {
     }
 
     if (data.app || data.browser) {
       iframe.addEventListener('mozbrowsershowmodalprompt', function(e) {
         is(e.detail.message, 'success', data.message);
 
         i++;
         if (i >= gData.length) {
-          SpecialPowers.setAllAppsLaunchable(originalAllAppsLaunchable);
           SimpleTest.finish();
         } else {
           gTestRunner.next();
         }
       });
     }
 
     iframe.src = data.src + '?' + data.action + '&' + data.isnull;
--- a/extensions/cookie/test/test_app_uninstall_cookies.html
+++ b/extensions/cookie/test/test_app_uninstall_cookies.html
@@ -45,45 +45,16 @@ var cookies = [
     loadContext: null },
   { cookieName: 'LCC_App_BrowT_PrivF',
     loadContext: null },
   { cookieName: 'AppUninstall_Witness',
     loadContext: new LoadContextCallback(0, false, false, 1) },
 ];
 var counter = 0;
 
-function confirmNextInstall() {
-  var panel = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIWebNavigation)
-                        .QueryInterface(Ci.nsIDocShell)
-                        .chromeEventHandler.ownerDocument.defaultView
-                        .PopupNotifications.panel
-
-  panel.addEventListener("popupshown", function() {
-    panel.removeEventListener("popupshown", arguments.callee, false);
-    this.childNodes[0].button.doCommand();
-  }, false);
-}
-
-// If aAppId = -1, returns permissions count, regardless of app.
-function getPermissionCountForApp(aAppId) {
-  var nbPermissions = 0;
-  var enumerator = permManager.enumerator;
-
-  while (enumerator.hasMoreElements()) {
-    var permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
-
-    if (permission.appId == aAppId || aAppId == -1) {
-      nbPermissions++;
-    }
-  }
-
-  return nbPermissions;
-}
-
 function getCookiesCountForApp(aAppId) {
   var nbCookies = 0;
   var enumerator = cookieMng.getCookiesForApp(aAppId, false);
 
   while (enumerator.hasMoreElements()) {
     enumerator.getNext();
     nbCookies++;
   }
@@ -140,52 +111,43 @@ function setNextCookie(request, data, co
     // all cookies set: switch to checking them
     counter = 0;
     checkCookie();
   } else {
     setCookie();
   }
 }
 
-var previousDryRunValue = null;
-try {
-  previousDryRunValue = SpecialPowers.getBoolPref('browser.mozApps.installer.dry_run');
-} catch(e) {
-}
-
-SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', true);
-
-var previousCookiePrefValue = SpecialPowers.getIntPref('network.cookie.cookieBehavior');
-
-SpecialPowers.setIntPref('network.cookie.cookieBehavior', 0);
-
 permManager.addFromPrincipal(window.document.nodePrincipal, "webapps-manage",
                              Ci.nsIPermissionManager.ALLOW_ACTION);
 
-var gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
+SimpleTest.registerCleanupFunction(() =>
+  permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
+                                  Ci.nsIPermissionManager.ALLOW_ACTION)
+);
 
-confirmNextInstall();
+var gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
 
 var gTestAppId = 0;
 var gCurrentCookiesCount = 0;
 
-navigator.mozApps.install(gManifestURL, null).onsuccess = function() {
+function onInstall() {
   gTestAppId = appsService.getAppLocalIdByManifestURL(gManifestURL);
 
   cookies[0].loadContext = new LoadContextCallback(gTestAppId, false, false, 1);
   cookies[1].loadContext = new LoadContextCallback(gTestAppId, true, false, 1);
 
   is(getCookiesCountForApp(gTestAppId), 0, "App should have no cookies");
 
   httpserver.registerPathHandler(cookieSetPath, cookieSetHandler);
   httpserver.registerPathHandler(cookieCheckPath, cookieCheckHandler);
   httpserver.start(4444);
 
   setCookie();
-};
+}
 
 function checkCookie() {
   var appCookiesCount = getCookiesCountForApp(gTestAppId);
   is(appCookiesCount, 2, "App should have two cookies");
 
   gCurrentCookiesCount = getCookiesCount() - appCookiesCount;
 
   // Not installed means not installed as native app.
@@ -194,28 +156,26 @@ function checkCookie() {
       var app = this.result[i];
       if (app.manifestURL == gManifestURL) {
         navigator.mozApps.mgmt.uninstall(app).onsuccess = function() {
           is(getCookiesCountForApp(gTestAppId), 0, "App should have no cookies");
 
           is(getCookiesCount(), gCurrentCookiesCount,
              "Number of cookies should not have changed");
 
-          SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', previousDryRunValue);
-          SpecialPowers.setIntPref('network.cookie.cookieBehavior', previousCookiePrefValue);
-          permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
-                                       Ci.nsIPermissionManager.ALLOW_ACTION);
-
           httpserver.stop(function() {
             SimpleTest.finish();
           });
-
-          return;
         };
       }
     }
   };
 }
 
+SpecialPowers.pushPrefEnv({set: [['network.cookie.cookieBehavior', 0]]}, () =>
+  SpecialPowers.autoConfirmAppInstall(() =>
+    navigator.mozApps.install(gManifestURL, null).onsuccess = onInstall
+  )
+);
 </script>
 </pre>
 </body>
 </html>
--- a/extensions/cookie/test/test_app_uninstall_permissions.html
+++ b/extensions/cookie/test/test_app_uninstall_permissions.html
@@ -29,60 +29,43 @@ var permManager = Cc["@mozilla.org/permi
                     .getService(Ci.nsIPermissionManager);
 var appsService = Cc['@mozilla.org/AppsService;1']
                     .getService(Ci.nsIAppsService);
 var secMan = Cc['@mozilla.org/scriptsecuritymanager;1']
                .getService(Ci.nsIScriptSecurityManager);
 var ioService = Cc["@mozilla.org/network/io-service;1"]
                   .getService(Components.interfaces.nsIIOService);
 
-function confirmNextInstall() {
-  var panel = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIWebNavigation)
-                        .QueryInterface(Ci.nsIDocShell)
-                        .chromeEventHandler.ownerDocument.defaultView
-                        .PopupNotifications.panel
-
-  panel.addEventListener("popupshown", function() {
-    panel.removeEventListener("popupshown", arguments.callee, false);
-    this.childNodes[0].button.doCommand();
-  }, false);
-}
-
 // If aAppId = -1, returns permissions count, regardless of app.
 function getPermissionCountForApp(aAppId) {
   var nbPermissions = 0;
   var enumerator = permManager.enumerator;
 
   while (enumerator.hasMoreElements()) {
     var permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
 
     if (permission.appId == aAppId || aAppId == -1) {
       nbPermissions++;
     }
   }
 
   return nbPermissions;
 }
 
-var previousDryRunValue = null;
-try {
-  previousDryRunValue = SpecialPowers.getBoolPref('browser.mozApps.installer.dry_run');
-} catch(e) {
-}
-
-SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', true);
 permManager.addFromPrincipal(window.document.nodePrincipal, "webapps-manage",
                              Ci.nsIPermissionManager.ALLOW_ACTION);
 
+SimpleTest.registerCleanupFunction(() =>
+  permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
+                                  Ci.nsIPermissionManager.ALLOW_ACTION)
+);
+
 var gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
 
-confirmNextInstall();
-
-navigator.mozApps.install(gManifestURL, null).onsuccess = function() {
+function onInstall() {
   var testAppId = appsService.getAppLocalIdByManifestURL(gManifestURL);
 
   is(getPermissionCountForApp(testAppId), 0, "App should have no permission");
 
   var currentPermissionCount = getPermissionCountForApp(-1);
 
   var principal = secMan.getAppCodebasePrincipal(ioService.newURI("http://www.example.com", null, null),
                                                  testAppId, false);
@@ -107,23 +90,23 @@ navigator.mozApps.install(gManifestURL, 
       var app = this.result[i];
       if (app.manifestURL == gManifestURL) {
         navigator.mozApps.mgmt.uninstall(app).onsuccess = function() {
           is(getPermissionCountForApp(testAppId), 0, "App should have no permissions");
 
           is(getPermissionCountForApp(-1), currentPermissionCount,
              "Number of permissions should not have changed");
 
-          SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', previousDryRunValue);
-          permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
-                                       Ci.nsIPermissionManager.ALLOW_ACTION);
           SimpleTest.finish();
-          return;
         };
       }
     }
   };
-};
+}
+
+SpecialPowers.autoConfirmAppInstall(() =>
+  navigator.mozApps.install(gManifestURL, null).onsuccess = onInstall
+);
 
 </script>
 </pre>
 </body>
 </html>
--- a/gfx/2d/SourceSurfaceSkia.cpp
+++ b/gfx/2d/SourceSurfaceSkia.cpp
@@ -68,22 +68,17 @@ SourceSurfaceSkia::InitFromData(unsigned
   temp.setConfig(GfxFormatToSkiaConfig(aFormat), aSize.width, aSize.height, aStride);
   temp.setPixels(aData);
 
   if (!temp.copyTo(&mBitmap, GfxFormatToSkiaColorType(aFormat))) {
     return false;
   }
 
   if (aFormat == SurfaceFormat::B8G8R8X8) {
-    mBitmap.lockPixels();
-    // We have to manually set the A channel to be 255 as Skia doesn't understand BGRX
-    ConvertBGRXToBGRA(reinterpret_cast<unsigned char*>(mBitmap.getPixels()), aSize, mBitmap.rowBytes());
-    mBitmap.unlockPixels();
-    mBitmap.notifyPixelsChanged();
-    mBitmap.setAlphaType(kOpaque_SkAlphaType);
+    mBitmap.setAlphaType(kIgnore_SkAlphaType);
   }
 
   mSize = aSize;
   mFormat = aFormat;
   mStride = mBitmap.rowBytes();
   return true;
 }
 
--- a/gfx/layers/ipc/ImageBridgeChild.cpp
+++ b/gfx/layers/ipc/ImageBridgeChild.cpp
@@ -303,30 +303,39 @@ bool ImageBridgeChild::IsCreated()
 }
 
 void ImageBridgeChild::StartUp()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!");
   ImageBridgeChild::StartUpOnThread(new Thread("ImageBridgeChild"));
 }
 
+#ifdef MOZ_NUWA_PROCESS
+#include "ipc/Nuwa.h"
+#endif
+
 static void
 ConnectImageBridgeInChildProcess(Transport* aTransport,
                                  ProcessHandle aOtherProcess)
 {
   // Bind the IPC channel to the image bridge thread.
   sImageBridgeChildSingleton->Open(aTransport, aOtherProcess,
                                    XRE_GetIOMessageLoop(),
                                    ipc::ChildSide);
+#ifdef MOZ_NUWA_PROCESS
+  if (IsNuwaProcess()) {
+    sImageBridgeChildThread
+      ->message_loop()->PostTask(FROM_HERE,
+                                 NewRunnableFunction(NuwaMarkCurrentThread,
+                                                     (void (*)(void *))nullptr,
+                                                     (void *)nullptr));
+  }
+#endif
 }
 
-#ifdef MOZ_NUWA_PROCESS
-#include "ipc/Nuwa.h"
-#endif
-
 static void ReleaseImageClientNow(ImageClient* aClient)
 {
   MOZ_ASSERT(InImageBridgeChildThread());
   aClient->Release();
 }
 
 // static
 void ImageBridgeChild::DispatchReleaseImageClient(ImageClient* aClient)
@@ -523,26 +532,16 @@ ImageBridgeChild::StartUpInChildProcess(
     return nullptr;
   }
 
   sImageBridgeChildThread = new Thread("ImageBridgeChild");
   if (!sImageBridgeChildThread->Start()) {
     return nullptr;
   }
 
-#ifdef MOZ_NUWA_PROCESS
-  if (IsNuwaProcess()) {
-    sImageBridgeChildThread
-      ->message_loop()->PostTask(FROM_HERE,
-                                 NewRunnableFunction(NuwaMarkCurrentThread,
-                                                     (void (*)(void *))nullptr,
-                                                     (void *)nullptr));
-  }
-#endif
-
   sImageBridgeChildSingleton = new ImageBridgeChild();
   sImageBridgeChildSingleton->GetMessageLoop()->PostTask(
     FROM_HERE,
     NewRunnableFunction(ConnectImageBridgeInChildProcess,
                         aTransport, processHandle));
 
   return sImageBridgeChildSingleton;
 }
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1118,17 +1118,17 @@ gfxFontFamily::FindFontForChar(GlobalFon
         int32_t rank = 0;
 
         if (fe->TestCharacterMap(aMatchData->mCh)) {
             rank += RANK_MATCHED_CMAP;
             aMatchData->mCount++;
 #ifdef PR_LOGGING
             PRLogModuleInfo *log = gfxPlatform::GetLog(eGfxLog_textrun);
 
-            if (MOZ_UNLIKELY(log)) {
+            if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_DEBUG))) {
                 uint32_t unicodeRange = FindCharUnicodeRange(aMatchData->mCh);
                 uint32_t script = GetScriptCode(aMatchData->mCh);
                 PR_LOG(log, PR_LOG_DEBUG,\
                        ("(textrun-systemfallback-fonts) char: u+%6.6x "
                         "unicode-range: %d script: %d match: [%s]\n",
                         aMatchData->mCh,
                         unicodeRange, script,
                         NS_ConvertUTF16toUTF8(fe->Name()).get()));
@@ -5012,17 +5012,17 @@ gfxFontGroup::InitTextRun(gfxContext *aC
     PRLogModuleInfo *log = (mStyle.systemFont ?
                             gfxPlatform::GetLog(eGfxLog_textrunui) :
                             gfxPlatform::GetLog(eGfxLog_textrun));
 #endif
 
     if (sizeof(T) == sizeof(uint8_t) && !transformedString) {
 
 #ifdef PR_LOGGING
-        if (MOZ_UNLIKELY(log)) {
+        if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) {
             nsAutoCString lang;
             mStyle.language->ToUTF8String(lang);
             nsAutoCString str((const char*)aString, aLength);
             PR_LOG(log, PR_LOG_WARNING,\
                    ("(%s) fontgroup: [%s] lang: %s script: %d len %d "
                     "weight: %d width: %d style: %s size: %6.2f %d-byte "
                     "TEXTRUN [%s] ENDTEXTRUN\n",
                     (mStyle.systemFont ? "textrunui" : "textrun"),
@@ -5056,17 +5056,17 @@ gfxFontGroup::InitTextRun(gfxContext *aC
         // the font matching process below
         gfxScriptItemizer scriptRuns(textPtr, aLength);
 
         uint32_t runStart = 0, runLimit = aLength;
         int32_t runScript = MOZ_SCRIPT_LATIN;
         while (scriptRuns.Next(runStart, runLimit, runScript)) {
 
 #ifdef PR_LOGGING
-            if (MOZ_UNLIKELY(log)) {
+            if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) {
                 nsAutoCString lang;
                 mStyle.language->ToUTF8String(lang);
                 uint32_t runLen = runLimit - runStart;
                 PR_LOG(log, PR_LOG_WARNING,\
                        ("(%s) fontgroup: [%s] lang: %s script: %d len %d "
                         "weight: %d width: %d style: %s size: %6.2f %d-byte "
                         "TEXTRUN [%s] ENDTEXTRUN\n",
                         (mStyle.systemFont ? "textrunui" : "textrun"),
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -443,17 +443,17 @@ gfxPlatformFontList::SystemFindFontForCh
         fontEntry = GlobalFontFallback(aCh, aRunScript, aStyle, cmapCount,
                                        &fallbackFamily);
     }
     TimeDuration elapsed = TimeStamp::Now() - start;
 
 #ifdef PR_LOGGING
     PRLogModuleInfo *log = gfxPlatform::GetLog(eGfxLog_textrun);
 
-    if (MOZ_UNLIKELY(log)) {
+    if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) {
         uint32_t unicodeRange = FindCharUnicodeRange(aCh);
         int32_t script = mozilla::unicode::GetScriptCode(aCh);
         PR_LOG(log, PR_LOG_WARNING,\
                ("(textrun-systemfallback-%s) char: u+%6.6x "
                  "unicode-range: %d script: %d match: [%s]"
                 " time: %dus cmaps: %d\n",
                 (common ? "common" : "global"), aCh,
                  unicodeRange, script,
--- a/ipc/glue/MessageLink.cpp
+++ b/ipc/glue/MessageLink.cpp
@@ -5,16 +5,21 @@
  * 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 "mozilla/ipc/MessageLink.h"
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/ipc/BrowserProcessSubThread.h"
 #include "mozilla/ipc/ProtocolUtils.h"
 
+#ifdef MOZ_NUWA_PROCESS
+#include "ipc/Nuwa.h"
+#include "mozilla/Preferences.h"
+#endif
+
 #include "nsDebug.h"
 #include "nsISupportsImpl.h"
 #include "nsXULAppAPI.h"
 
 using namespace mozilla;
 using namespace std;
 
 template<>
@@ -119,16 +124,27 @@ ProcessLink::Open(mozilla::ipc::Transpor
             // Transport::Connect() has already been called.  Take
             // over the channel from the previous listener and process
             // any queued messages.
             mIOLoop->PostTask(
                 FROM_HERE,
                 NewRunnableMethod(this, &ProcessLink::OnTakeConnectedChannel));
         }
 
+#ifdef MOZ_NUWA_PROCESS
+        if (IsNuwaProcess() &&
+            Preferences::GetBool("dom.ipc.processPrelaunch.testMode")) {
+            // The pref value is turned on in a deadlock test against the Nuwa
+            // process. The sleep here makes it easy to trigger the deadlock
+            // that an IPC channel is still opening but the worker loop is
+            // already frozen.
+            sleep(5);
+        }
+#endif
+
         // Should not wait here if something goes wrong with the channel.
         while (!mChan->Connected() && mChan->mChannelState != ChannelError) {
             mChan->mMonitor->Wait();
         }
     }
 }
 
 void
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/926155-1-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <div style="overflow-x:hidden">
+      <div id="testdiv" style="width:200px;margin-top:50px;height:10px;background-color:green"></div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/926155-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <body>
+    <div style="overflow:hidden;height:100px">
+      <div style="overflow-x:hidden;position:sticky;top:50px">
+        <div id="testdiv" style="width:10px;height:10px;background-color:green"></div>
+      </div>
+    </div>
+  <script>
+    function doTest() {
+      var x = document.getElementById('testdiv');
+      x.style.width = "200px";
+      document.documentElement.className = "";
+    }
+    document.addEventListener("MozReftestInvalidate", doTest, false);
+  </script>
+  </body>
+</html>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1776,16 +1776,17 @@ skip-if(B2G&&browserIsRemote) == 858803-
 == 883987-1f.html 883987-1-ref.html
 == 890495-1.html 890495-1-ref.html
 == 894931-1.html 894931-1-ref.html
 == 897491-1.html 897491-1-ref.html
 == 897491-2.html 897491-2-ref.html
 fuzzy(1,10000) fuzzy-if(Android&&AndroidVersion>=15,5,10000) == 902330-1.html 902330-1-ref.html
 fuzzy-if(Android,8,400) == 906199-1.html 906199-1-ref.html
 == 921716-1.html 921716-1-ref.html
+== 926155-1.html 926155-1-ref.html
 fuzzy-if(cocoaWidget,1,40) == 928607-1.html 928607-1-ref.html
 == 931464-1.html 931464-1-ref.html
 == 931853.html 931853-ref.html
 == 931853-quirks.html 931853-quirks-ref.html
 fuzzy-if(OSX==10.6,2,30) skip-if(B2G&&browserIsRemote) == 933264-1.html 933264-1-ref.html
 == 936670-1.svg 936670-1-ref.svg
 == 941940-1.html 941940-1-ref.html
 fails == 942017.html 942017-ref.html # bug 942017
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -982,26 +982,27 @@ void MediaPipelineTransmit::PipelineList
         }
         break;
       case AUDIO_FORMAT_S16:
         {
           const short* buf = static_cast<const short *>(chunk.mChannelData[0]);
           ConvertAudioSamplesWithScale(buf, samples, chunk.mDuration, chunk.mVolume);
         }
         break;
+      case AUDIO_FORMAT_SILENCE:
+        memset(samples, 0, chunk.mDuration * sizeof(samples[0]));
+        break;
       default:
         MOZ_ASSERT(PR_FALSE);
         return;
         break;
     }
   } else {
     // This means silence.
-    for (uint32_t i = 0; i < chunk.mDuration; ++i) {
-      samples[i] = 0;
-    }
+    memset(samples, 0, chunk.mDuration * sizeof(samples[0]));
   }
 
   MOZ_ASSERT(!(rate%100)); // rate should be a multiple of 100
 
   // Check if the rate has changed since the last time we came through
   // I realize it may be overkill to check if the rate has changed, but
   // I believe it is possible (e.g. if we change sources) and it costs us
   // very little to handle this case
--- a/mobile/locales/en-US/searchplugins/bing.xml
+++ b/mobile/locales/en-US/searchplugins/bing.xml
@@ -8,18 +8,18 @@
 <Url type="application/x-suggestions+json" template="http://api.bing.com/osjson.aspx">
   <Param name="query" value="{searchTerms}"/>
   <Param name="form" value="OSDJAS"/>
   <Param name="language" value="{moz:locale}"/>
 </Url>
 <!-- this is effectively x-moz-phonesearch, but search service expects a text/html entry -->
 <Url type="text/html" method="GET" template="http://www.bing.com/search">
   <Param name="q" value="{searchTerms}" />
-  <MozParam name="pc" value="MOZB" />
-  <MozParam name="form" value="MOZMBA" />
+  <Param name="pc" value="MOZB" />
+  <Param name="form" value="MOZMBA" />
 </Url>
 <Url type="application/x-moz-tabletsearch" method="GET" template="http://www.bing.com/search">
   <Param name="q" value="{searchTerms}" />
-  <MozParam name="pc" value="MOZA" />
-  <MozParam name="form" value="MOZAT" />
+  <Param name="pc" value="MOZA" />
+  <Param name="form" value="MOZAT" />
 </Url>
 <SearchForm>http://www.bing.com</SearchForm>
 </SearchPlugin>
--- a/mobile/locales/en-US/searchplugins/yahoo.xml
+++ b/mobile/locales/en-US/searchplugins/yahoo.xml
@@ -6,12 +6,12 @@
 <ShortName>Yahoo</ShortName>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
 <Url type="application/x-suggestions+json" method="GET"
      template="https://search.yahoo.com/sugg/ff?output=fxjson&amp;appid=ffm&amp;command={searchTerms}" />
 <Url type="text/html" method="GET" template="https://search.yahoo.com/search">
   <Param name="p" value="{searchTerms}" />
   <Param name="ei" value="UTF-8" />
-  <MozParam name="fr" value="mozilla_mobile_search" />
+  <Param name="fr" value="mozilla_mobile_search" />
 </Url>
 <SearchForm>https://search.yahoo.com/</SearchForm>
 </SearchPlugin>
--- a/netwerk/streamconv/converters/nsIndexedToHTML.cpp
+++ b/netwerk/streamconv/converters/nsIndexedToHTML.cpp
@@ -242,20 +242,16 @@ nsIndexedToHTML::DoOnStartRequest(nsIReq
             uri->SetPath(path);
         }
         if (!path.EqualsLiteral("/")) {
             rv = uri->Resolve(NS_LITERAL_CSTRING(".."), parentStr);
             if (NS_FAILED(rv)) return rv;
         }
     }
 
-    nsXPIDLCString encoding;
-    rv = uri->GetOriginCharset(encoding);
-    if (NS_FAILED(rv)) return rv;
-
     buffer.AppendLiteral("<style type=\"text/css\">\n"
                          ":root {\n"
                          "  font-family: sans-serif;\n"
                          "}\n"
                          "img {\n"
                          "  border: 0;\n"
                          "}\n"
                          "th {\n"
@@ -484,16 +480,23 @@ nsIndexedToHTML::DoOnStartRequest(nsIReq
     // Everything needs to end in a /,
     // otherwise we end up linking to file:///foo/dirfile
 
     if (!mTextToSubURI) {
         mTextToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
         if (NS_FAILED(rv)) return rv;
     }
 
+    nsXPIDLCString encoding;
+    rv = uri->GetOriginCharset(encoding);
+    if (NS_FAILED(rv)) return rv;
+    if (encoding.IsEmpty()) {
+      encoding.AssignLiteral("UTF-8");
+    }
+
     nsXPIDLString unEscapeSpec;
     rv = mTextToSubURI->UnEscapeAndConvert(encoding, titleUri.get(),
                                            getter_Copies(unEscapeSpec));
     // unescape may fail because
     // 1. file URL may be encoded in platform charset for backward compatibility
     // 2. query part may not be encoded in UTF-8 (see bug 261929)
     // so try the platform's default if this is file url
     if (NS_FAILED(rv) && isSchemeFile) {
--- a/security/manager/ssl/src/TransportSecurityInfo.cpp
+++ b/security/manager/ssl/src/TransportSecurityInfo.cpp
@@ -787,17 +787,17 @@ AppendErrorTextMismatch(const nsString &
     // currently CERT_FindNSStringExtension is not being exported by NSS.
     // If it gets exported, enable the following line.
     //   certName = CERT_FindNSStringExtension(nssCert, SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME);
     // However, it has been discussed to treat the extension as obsolete and ignore it.
     if (!certName)
       certName = CERT_GetCommonName(&nssCert->subject);
     if (certName) {
       ++nameCount;
-      allNames.AssignASCII(certName);
+      allNames.Assign(NS_ConvertUTF8toUTF16(certName));
       PORT_Free(certName);
     }
   }
 
   if (nameCount > 1) {
     nsString message;
     rv = component->GetPIPNSSBundleString("certErrorMismatchMultiple", 
                                           message);
--- a/security/manager/ssl/tests/unit/test_ocsp_stapling.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_stapling.js
@@ -40,16 +40,17 @@ function add_tests_in_mode(useMozillaPKI
   add_ocsp_test("ocsp-stapling-unknown.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-good-other.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-none.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-expired.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-expired-fresh-ca.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-skip-responseBytes.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-critical-extension.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-noncritical-extension.example.com", Cr.NS_OK, false);
+  add_ocsp_test("ocsp-stapling-empty-extensions.example.com", Cr.NS_OK, false);
 
   // Now test OCSP stapling
   // The following error codes are defined in security/nss/lib/util/SECerrs.h
 
   add_ocsp_test("ocsp-stapling-good.example.com", Cr.NS_OK, true);
 
   add_ocsp_test("ocsp-stapling-revoked.example.com",
                 getXPCOMStatusFromNSS(SEC_ERROR_REVOKED_CERTIFICATE), true);
@@ -121,16 +122,18 @@ function add_tests_in_mode(useMozillaPKI
   }
 
   add_ocsp_test("ocsp-stapling-critical-extension.example.com",
                 useMozillaPKIX
                   ? getXPCOMStatusFromNSS(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION)
                   : Cr.NS_OK, // TODO(bug 987426): NSS doesn't handle unknown critical extensions
                 true);
   add_ocsp_test("ocsp-stapling-noncritical-extension.example.com", Cr.NS_OK, true);
+  // TODO(bug 997994): Disallow empty Extensions in responses
+  add_ocsp_test("ocsp-stapling-empty-extensions.example.com", Cr.NS_OK, true);
 
   add_ocsp_test("ocsp-stapling-delegated-included.example.com", Cr.NS_OK, true);
   add_ocsp_test("ocsp-stapling-delegated-included-last.example.com", Cr.NS_OK, true);
   add_ocsp_test("ocsp-stapling-delegated-missing.example.com",
                 getXPCOMStatusFromNSS(SEC_ERROR_OCSP_INVALID_SIGNING_CERT), true);
   add_ocsp_test("ocsp-stapling-delegated-missing-multiple.example.com",
                 getXPCOMStatusFromNSS(SEC_ERROR_OCSP_INVALID_SIGNING_CERT), true);
   add_ocsp_test("ocsp-stapling-delegated-no-extKeyUsage.example.com",
@@ -148,18 +151,18 @@ function add_tests_in_mode(useMozillaPKI
 }
 
 function check_ocsp_stapling_telemetry() {
   let histogram = Cc["@mozilla.org/base/telemetry;1"]
                     .getService(Ci.nsITelemetry)
                     .getHistogramById("SSL_OCSP_STAPLING")
                     .snapshot();
   do_check_eq(histogram.counts[0], 2 * 0); // histogram bucket 0 is unused
-  do_check_eq(histogram.counts[1], 4 + 5); // 4 or 5 connections with a good response (bug 987426)
-  do_check_eq(histogram.counts[2], 2 * 17); // 17 connections with no stapled resp.
+  do_check_eq(histogram.counts[1], 5 + 6); // 5 or 6 connections with a good response (bug 987426)
+  do_check_eq(histogram.counts[2], 2 * 18); // 18 connections with no stapled resp.
   do_check_eq(histogram.counts[3], 2 * 0); // 0 connections with an expired response
   do_check_eq(histogram.counts[4], 19 + 17); // 19 or 17 connections with bad responses (bug 979070, bug 987426)
   run_next_test();
 }
 
 function run_test() {
   do_get_profile();
 
--- a/security/manager/ssl/tests/unit/tlsserver/cmd/OCSPStaplingServer.cpp
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/OCSPStaplingServer.cpp
@@ -35,16 +35,17 @@ const OCSPHost sOCSPHosts[] =
   { "ocsp-stapling-trylater.example.com", ORTTryLater, nullptr },
   { "ocsp-stapling-needssig.example.com", ORTNeedsSig, nullptr },
   { "ocsp-stapling-unauthorized.example.com", ORTUnauthorized, nullptr },
   { "ocsp-stapling-with-intermediate.example.com", ORTGood, "ocspEEWithIntermediate" },
   { "ocsp-stapling-bad-signature.example.com", ORTBadSignature, nullptr },
   { "ocsp-stapling-skip-responseBytes.example.com", ORTSkipResponseBytes, nullptr },
   { "ocsp-stapling-critical-extension.example.com", ORTCriticalExtension, nullptr },
   { "ocsp-stapling-noncritical-extension.example.com", ORTNoncriticalExtension, nullptr },
+  { "ocsp-stapling-empty-extensions.example.com", ORTEmptyExtensions, nullptr },
   { "ocsp-stapling-delegated-included.example.com", ORTDelegatedIncluded, "delegatedSigner" },
   { "ocsp-stapling-delegated-included-last.example.com", ORTDelegatedIncludedLast, "delegatedSigner" },
   { "ocsp-stapling-delegated-missing.example.com", ORTDelegatedMissing, "delegatedSigner" },
   { "ocsp-stapling-delegated-missing-multiple.example.com", ORTDelegatedMissingMultiple, "delegatedSigner" },
   { "ocsp-stapling-delegated-no-extKeyUsage.example.com", ORTDelegatedIncluded, "invalidDelegatedSignerNoExtKeyUsage" },
   { "ocsp-stapling-delegated-from-intermediate.example.com", ORTDelegatedIncluded, "invalidDelegatedSignerFromIntermediate" },
   { "ocsp-stapling-delegated-keyUsage-crlSigning.example.com", ORTDelegatedIncluded, "invalidDelegatedSignerKeyUsageCrlSigning" },
   { "ocsp-stapling-delegated-wrong-extKeyUsage.example.com", ORTDelegatedIncluded, "invalidDelegatedSignerWrongExtKeyUsage" },
--- a/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.cpp
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.cpp
@@ -139,16 +139,19 @@ GetOCSPResponseForType(OCSPResponseType 
     }
     extension.critical = (aORT == ORTCriticalExtension);
     static const uint8_t value[2] = { 0x05, 0x00 };
     extension.value.data = const_cast<uint8_t*>(value);
     extension.value.len = PR_ARRAY_SIZE(value);
     extension.next = nullptr;
     context.extensions = &extension;
   }
+  if (aORT == ORTEmptyExtensions) {
+    context.includeEmptyExtensions = true;
+  }
 
   if (!context.signerCert) {
     context.signerCert = CERT_DupCertificate(context.issuerCert.get());
   }
 
   SECItem* response = CreateEncodedOCSPResponse(context);
   if (!response) {
     PrintPRError("CreateEncodedOCSPResponse failed");
--- a/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.h
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.h
@@ -27,16 +27,17 @@ enum OCSPResponseType
   ORTSrverr,           // the response indicates there was a server error
   ORTTryLater,         // the responder replied with "try again later"
   ORTNeedsSig,         // the response needs a signature
   ORTUnauthorized,     // the responder is not authorized for this certificate
   ORTBadSignature,     // the response has a signature that does not verify
   ORTSkipResponseBytes, // the response does not include responseBytes
   ORTCriticalExtension, // the response includes a critical extension
   ORTNoncriticalExtension, // the response includes an extension that is not critical
+  ORTEmptyExtensions,  // the response includes a SEQUENCE OF Extension that is empty
   ORTDelegatedIncluded, // the response is signed by an included delegated responder
   ORTDelegatedIncludedLast, // same, but multiple other certificates are included
   ORTDelegatedMissing, // the response is signed by a not included delegated responder
   ORTDelegatedMissingMultiple, // same, but multiple other certificates are included
 };
 
 struct OCSPHost
 {
--- a/security/pkix/lib/pkixocsp.cpp
+++ b/security/pkix/lib/pkixocsp.cpp
@@ -853,21 +853,25 @@ CheckExtensionForCriticality(der::Input&
 
   if (ExpectTagAndGetLength(input, der::OCTET_STRING, toSkip)
         != der::Success) {
     return der::Failure;
   }
   return input.Skip(toSkip);
 }
 
+// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
 static der::Result
 CheckExtensionsForCriticality(der::Input& input)
 {
+  // TODO(bug 997994): some responders include an empty SEQUENCE OF
+  // Extension, which is invalid (der::MayBeEmpty should really be
+  // der::MustNotBeEmpty).
   return der::NestedOf(input, der::SEQUENCE, der::SEQUENCE,
-                       der::MustNotBeEmpty, CheckExtensionForCriticality);
+                       der::MayBeEmpty, CheckExtensionForCriticality);
 }
 
 //   1. The certificate identified in a received response corresponds to
 //      the certificate that was identified in the corresponding request;
 //   2. The signature on the response is valid;
 //   3. The identity of the signer matches the intended recipient of the
 //      request;
 //   4. The signer is currently authorized to provide a response for the
--- a/security/pkix/test/lib/pkixtestutil.cpp
+++ b/security/pkix/test/lib/pkixtestutil.cpp
@@ -125,16 +125,17 @@ OCSPResponseContext::OCSPResponseContext
   , nextUpdate(time + 10 * PR_USEC_PER_SEC)
   , includeNextUpdate(true)
   , certIDHashAlg(SEC_OID_SHA1)
   , certStatus(0)
   , revocationTime(0)
   , badSignature(false)
   , responderIDType(ByKeyHash)
   , extensions(nullptr)
+  , includeEmptyExtensions(false)
 {
   for (size_t i = 0; i < MaxIncludedCertificates; i++) {
     includedCertificates[i] = nullptr;
   }
 }
 
 static SECItem* ResponseBytes(OCSPResponseContext& context);
 static SECItem* BasicOCSPResponse(OCSPResponseContext& context);
@@ -508,17 +509,17 @@ ResponseData(OCSPResponseContext& contex
     return nullptr;
   }
   SECItem* responsesNested = EncodeNested(context.arena, der::SEQUENCE,
                                           responses);
   if (!responsesNested) {
     return nullptr;
   }
   SECItem* responseExtensions = nullptr;
-  if (context.extensions) {
+  if (context.extensions || context.includeEmptyExtensions) {
     responseExtensions = Extensions(context);
   }
 
   Output output;
   if (output.Add(responderID) != der::Success) {
     return nullptr;
   }
   if (output.Add(producedAtEncoded) != der::Success) {
--- a/security/pkix/test/lib/pkixtestutil.h
+++ b/security/pkix/test/lib/pkixtestutil.h
@@ -62,16 +62,19 @@ public:
 
   enum ResponderIDType {
     ByName = 1,
     ByKeyHash = 2
   };
   ResponderIDType responderIDType;
 
   OCSPResponseExtension* extensions;
+  bool includeEmptyExtensions; // If true, include the extension wrapper
+                               // regardless of if there are any actual
+                               // extensions.
 };
 
 // The return value, if non-null, is owned by the arena in the context
 // and MUST NOT be freed.
 // This function does its best to respect the NSPR error code convention
 // (that is, if it returns null, calling PR_GetError() will return the
 // error of the failed operation). However, this is not guaranteed.
 SECItem* CreateEncodedOCSPResponse(OCSPResponseContext& context);
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -91,16 +91,19 @@ static struct sock_filter seccomp_filter
    * argument filtering */
   ALLOW_SYSCALL(ioctl),
   ALLOW_SYSCALL(close),
   ALLOW_SYSCALL(munmap),
   ALLOW_SYSCALL(mprotect),
   ALLOW_SYSCALL(writev),
   ALLOW_SYSCALL(clone),
   ALLOW_SYSCALL(brk),
+#if SYSCALL_EXISTS(set_thread_area)
+  ALLOW_SYSCALL(set_thread_area),
+#endif
 
   ALLOW_SYSCALL(getpid),
   ALLOW_SYSCALL(gettid),
   ALLOW_SYSCALL(getrusage),
   ALLOW_SYSCALL(madvise),
   ALLOW_SYSCALL(dup),
   ALLOW_SYSCALL(nanosleep),
   ALLOW_SYSCALL(poll),
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -91,16 +91,38 @@ function Tester(aTests, aDumper, aCallba
   this.Task = Components.utils.import("resource://gre/modules/Task.jsm", null).Task;
   this.Promise = Components.utils.import("resource://gre/modules/Promise.jsm", null).Promise;
   this.Assert = Components.utils.import("resource://testing-common/Assert.jsm", null).Assert;
 
   this.SimpleTestOriginal = {};
   SIMPLETEST_OVERRIDES.forEach(m => {
     this.SimpleTestOriginal[m] = this.SimpleTest[m];
   });
+
+  this._uncaughtErrorObserver = function({message, date, fileName, stack, lineNumber}) {
+    let text = "Once bug 991040 has landed, THIS ERROR WILL CAUSE A TEST FAILURE.\n" + message;
+    let error = text;
+    if (fileName || lineNumber) {
+      error = {
+        fileName: fileName,
+        lineNumber: lineNumber,
+        message: text,
+        toString: function() {
+          return text;
+        }
+      };
+    }
+    this.currentTest.addResult(
+      new testResult(
+	/*success*/ true,
+        /*name*/"A promise chain failed to handle a rejection",
+        /*error*/error,
+        /*known*/true,
+        /*stack*/stack));
+    }.bind(this);
 }
 Tester.prototype = {
   EventUtils: {},
   SimpleTest: {},
   Task: null,
   Promise: null,
   Assert: null,
 
@@ -146,16 +168,19 @@ Tester.prototype = {
     this._globalProperties = Object.keys(window);
     this._globalPropertyWhitelist = [
       "navigator", "constructor", "top",
       "Application",
       "__SS_tabsToRestore", "__SSi",
       "webConsoleCommandController",
     ];
 
+    this.Promise.Debugging.clearUncaughtErrorObservers();
+    this.Promise.Debugging.addUncaughtErrorObserver(this._uncaughtErrorObserver);
+
     if (this.tests.length)
       this.nextTest();
     else
       this.finish();
   },
 
   waitForWindowsState: function Tester_waitForWindowsState(aCallback) {
     let timedOut = this.currentTest && this.currentTest.timedOut;
@@ -198,42 +223,44 @@ Tester.prototype = {
       }
     }
 
     // Make sure the window is raised before each test.
     this.SimpleTest.waitForFocus(aCallback);
   },
 
   finish: function Tester_finish(aSkipSummary) {
+    this.Promise.Debugging.flushUncaughtErrors();
+
     var passCount = this.tests.reduce(function(a, f) a + f.passCount, 0);
     var failCount = this.tests.reduce(function(a, f) a + f.failCount, 0);
     var todoCount = this.tests.reduce(function(a, f) a + f.todoCount, 0);
 
     if (this.repeat > 0) {
       --this.repeat;
       this.currentTestIndex = -1;
       this.nextTest();
     }
     else{
       Services.console.unregisterListener(this);
       Services.obs.removeObserver(this, "chrome-document-global-created");
       Services.obs.removeObserver(this, "content-document-global-created");
-  
+      this.Promise.Debugging.clearUncaughtErrorObservers();
       this.dumper.dump("\nINFO TEST-START | Shutdown\n");
+
       if (this.tests.length) {
         this.dumper.dump("Browser Chrome Test Summary\n");
   
         this.dumper.dump("\tPassed: " + passCount + "\n" +
                          "\tFailed: " + failCount + "\n" +
                          "\tTodo: " + todoCount + "\n");
       } else {
         this.dumper.dump("TEST-UNEXPECTED-FAIL | (browser-test.js) | " +
                          "No tests to run. Did you pass an invalid --test-path?\n");
       }
-  
       this.dumper.dump("\n*** End BrowserChrome Test Results ***\n");
   
       this.dumper.done();
   
       // Tests complete, notify the callback and return
       this.callback(this.tests);
       this.callback = null;
       this.tests = null;
@@ -297,16 +324,18 @@ Tester.prototype = {
         try {
           func.apply(testScope);
         }
         catch (ex) {
           this.currentTest.addResult(new testResult(false, "Cleanup function threw an exception", ex, false));
         }
       };
 
+      this.Promise.Debugging.flushUncaughtErrors();
+
       let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
       if (winUtils.isTestControllingRefreshes) {
         this.currentTest.addResult(new testResult(false, "test left refresh driver under test control", "", false));
         winUtils.restoreNormalRefresh();
       }
 
       if (this.SimpleTest.isExpectingUncaughtException()) {
@@ -561,17 +590,17 @@ Tester.prototype = {
        this.currentTest.addResult(new testResult(false, "head.js import threw an exception", ex, false));
       }
     }
 
     // Import the test script.
     try {
       this._scriptLoader.loadSubScript(this.currentTest.path,
                                        this.currentTest.scope);
-
+      this.Promise.Debugging.flushUncaughtErrors();
       // Run the test
       this.lastStartTime = Date.now();
       if (this.currentTest.scope.__tasks) {
         // This test consists of tasks, added via the `add_task()` API.
         if ("test" in this.currentTest.scope) {
           throw "Cannot run both a add_task test and a normal test at the same time.";
         }
         this.Task.spawn(function() {
@@ -582,16 +611,17 @@ Tester.prototype = {
               yield task();
             } catch (ex) {
               let isExpected = !!this.SimpleTest.isExpectingUncaughtException();
               let stack = (typeof ex == "object" && "stack" in ex)?ex.stack:null;
               let name = "Uncaught exception";
               let result = new testResult(isExpected, name, ex, false, stack);
               currentTest.addResult(result);
             }
+            this.Promise.Debugging.flushUncaughtErrors();
             this.SimpleTest.info("Leaving test " + task.name);
           }
           this.finish();
         }.bind(currentScope));
       } else if ("generatorTest" in this.currentTest.scope) {
         if ("test" in this.currentTest.scope) {
           throw "Cannot run both a generator test and a normal test at the same time.";
         }
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -1290,17 +1290,17 @@ class Mochitest(MochitestUtilsMixin):
       elif mozinfo.isLinux and self.perl:
         # Run logsource through fix-linux-stack.pl (uses addr2line)
         # This method is preferred for developer machines, so we don't have to run "make buildsymbols".
         stackFixerCommand = [self.perl, os.path.join(self.utilityPath, "fix-linux-stack.pl")]
         stackFixerProcess = subprocess.Popen(stackFixerCommand, stdin=subprocess.PIPE,
                                              stdout=subprocess.PIPE)
         def fixFunc(line):
           stackFixerProcess.stdin.write(line + '\n')
-          return stackFixerProcess.stdout.readline().strip()
+          return stackFixerProcess.stdout.readline().rstrip()
 
         stackFixerFunction = fixFunc
 
       return (stackFixerFunction, stackFixerProcess)
 
     def finish(self, didTimeout):
       if self.stackFixerProcess:
         self.stackFixerProcess.communicate()
--- a/testing/mochitest/tests/SimpleTest/SimpleTest.js
+++ b/testing/mochitest/tests/SimpleTest/SimpleTest.js
@@ -886,16 +886,17 @@ SimpleTest.finish = function () {
                            + "SimpleTest.waitForExplicitFinish() if you need "
                            + "it.)");
     }
 
     if (parentRunner) {
         /* We're running in an iframe, and the parent has a TestRunner */
         parentRunner.testFinished(SimpleTest._tests);
     } else {
+        SpecialPowers.flushAllAppsLaunchable();
         SpecialPowers.flushPermissions(function () {
           SpecialPowers.flushPrefEnv(function() {
             SimpleTest.showReport();
           });
         });
     }
 };
 
--- a/testing/mochitest/tests/SimpleTest/TestRunner.js
+++ b/testing/mochitest/tests/SimpleTest/TestRunner.js
@@ -473,16 +473,17 @@ TestRunner.testFinished = function(tests
         } else {
             interstitialURL = "/tests/SimpleTest/iframe-between-tests.html";
         }
         TestRunner._makeIframe(interstitialURL, 0);
     }
 
     SpecialPowers.executeAfterFlushingMessageQueue(function() {
         cleanUpCrashDumpFiles();
+        SpecialPowers.flushAllAppsLaunchable();
         SpecialPowers.flushPermissions(function () { SpecialPowers.flushPrefEnv(runNextTest); });
     });
 };
 
 TestRunner.testUnloaded = function() {
     // If we're in a debug build, check assertion counts.  This code is
     // similar to the code in Tester_nextTest in browser-test.js used
     // for browser-chrome mochitests.
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -1026,21 +1026,28 @@ SpecialPowersAPI.prototype = {
   // The provided callback is invoked once the prompt is disabled.
   autoConfirmAppInstall: function(cb) {
     this.pushPrefEnv({set: [['dom.mozApps.auto_confirm_install', true]]}, cb);
   },
 
   // Allow tests to disable the per platform app validity checks so we can
   // test higher level WebApp functionality without full platform support.
   setAllAppsLaunchable: function(launchable) {
-    var message = {
+    this._sendSyncMessage("SPWebAppService", {
       op: "set-launchable",
       launchable: launchable
-    };
-    return this._sendSyncMessage("SPWebAppService", message);
+    });
+  },
+
+  // Restore the launchable property to its default value.
+  flushAllAppsLaunchable: function() {
+    this._sendSyncMessage("SPWebAppService", {
+      op: "set-launchable",
+      launchable: false
+    });
   },
 
   _proxiedObservers: {
     "specialpowers-http-notify-request": function(aMessage) {
       let uri = aMessage.json.uri;
       Services.obs.notifyObservers(null, "specialpowers-http-notify-request", uri);
     },
   },
--- a/toolkit/components/downloads/ApplicationReputation.cpp
+++ b/toolkit/components/downloads/ApplicationReputation.cpp
@@ -44,19 +44,17 @@
 
 using mozilla::Preferences;
 using mozilla::TimeStamp;
 using mozilla::Telemetry::Accumulate;
 using safe_browsing::ClientDownloadRequest;
 using safe_browsing::ClientDownloadRequest_SignatureInfo;
 using safe_browsing::ClientDownloadRequest_CertificateChain;
 
-// Preferences that we need to initialize the query. We may need another
-// preference than browser.safebrowsing.malware.enabled, or simply use
-// browser.safebrowsing.appRepURL. See bug 887041.
+// Preferences that we need to initialize the query.
 #define PREF_SB_APP_REP_URL "browser.safebrowsing.appRepURL"
 #define PREF_SB_MALWARE_ENABLED "browser.safebrowsing.malware.enabled"
 #define PREF_GENERAL_LOCALE "general.useragent.locale"
 #define PREF_DOWNLOAD_BLOCK_TABLE "urlclassifier.downloadBlockTable"
 #define PREF_DOWNLOAD_ALLOW_TABLE "urlclassifier.downloadAllowTable"
 
 // NSPR_LOG_MODULES=ApplicationReputation:5
 #if defined(PR_LOGGING)
@@ -123,16 +121,19 @@ private:
   // the Windows Authenticode API, if the binary is signed.
   ClientDownloadRequest_SignatureInfo mSignatureInfo;
 
   // The response from the application reputation query. This is read in chunks
   // as part of our nsIStreamListener implementation and may contain embedded
   // NULLs.
   nsCString mResponse;
 
+  // Returns true if the file is likely to be binary on Windows.
+  bool IsBinaryFile();
+
   // Clean up and call the callback. PendingLookup must not be used after this
   // function is called.
   nsresult OnComplete(bool shouldBlock, nsresult rv);
 
   // Wrapper function for nsIStreamListener.onStopRequest to make it easy to
   // guarantee calling the callback
   nsresult OnStopRequestInternal(nsIRequest *aRequest,
                                  nsISupports *aContext,
@@ -328,16 +329,44 @@ PendingLookup::PendingLookup(nsIApplicat
   LOG(("Created pending lookup [this = %p]", this));
 }
 
 PendingLookup::~PendingLookup()
 {
   LOG(("Destroying pending lookup [this = %p]", this));
 }
 
+bool
+PendingLookup::IsBinaryFile()
+{
+  nsString fileName;
+  nsresult rv = mQuery->GetSuggestedFileName(fileName);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+  return
+    // Executable extensions for MS Windows, from
+    // https://code.google.com/p/chromium/codesearch#chromium/src/chrome/common/safe_browsing/download_protection_util.cc&l=14
+    StringEndsWith(fileName, NS_LITERAL_STRING(".apk")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".bas")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".bat")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".cab")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".cmd")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".com")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".exe")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".hta")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".msi")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".pif")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".reg")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".scr")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".vb")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".vbs")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".zip"));
+}
+
 nsresult
 PendingLookup::LookupNext()
 {
   // We must call LookupNext or SendRemoteQuery upon return.
   // Look up all of the URLs that could whitelist this download.
   // Blacklist first.
   int index = mAnylistSpecs.Length() - 1;
   nsCString spec;
@@ -354,20 +383,24 @@ PendingLookup::LookupNext()
       spec = mAllowlistSpecs[index];
       mAllowlistSpecs.RemoveElementAt(index);
     }
   }
   if (index >= 0) {
     nsRefPtr<PendingDBLookup> lookup(new PendingDBLookup(this));
     return lookup->LookupSpec(spec, allowlistOnly);
   }
-  // There are no more URIs to check against local list, so send the remote
-  // query if we can.
-  // Revert to just ifdef XP_WIN when remote lookups are enabled (bug 933432)
-#if 0
+#ifdef XP_WIN
+  // There are no more URIs to check against local list. If the file is not
+  // eligible for remote lookup, bail.
+  if (!IsBinaryFile()) {
+    LOG(("Not eligible for remote lookups [this=%x]", this));
+    return OnComplete(false, NS_OK);
+  }
+  // Send the remote query if we are on Windows.
   nsresult rv = SendRemoteQuery();
   if (NS_FAILED(rv)) {
     return OnComplete(false, rv);
   }
   return NS_OK;
 #else
   return OnComplete(false, NS_OK);
 #endif
--- a/toolkit/components/downloads/test/unit/test_app_rep_windows.js
+++ b/toolkit/components/downloads/test/unit/test_app_rep_windows.js
@@ -200,30 +200,31 @@ add_task(function test_setup()
     return blob;
   }
 
   gHttpServer.registerPathHandler("/throw", function(request, response) {
     do_throw("We shouldn't be getting here");
   });
 
   gHttpServer.registerPathHandler("/download", function(request, response) {
+    do_print("Querying remote server for verdict");
     response.setHeader("Content-Type", "application/octet-stream", false);
     let buf = NetUtil.readInputStreamToString(
       request.bodyInputStream,
       request.bodyInputStream.available());
     do_print("Request length: " + buf.length);
     // A garbage response. By default this produces NS_CANNOT_CONVERT_DATA as
     // the callback status.
     let blob = "this is not a serialized protocol buffer";
     // We can't actually parse the protocol buffer here, so just switch on the
     // length instead of inspecting the contents.
-    if (buf.length == 35) {
+    if (buf.length == 45) {
       // evil.com
       blob = createVerdict(true);
-    } else if (buf.length == 38) {
+    } else if (buf.length == 48) {
       // mozilla.com
       blob = createVerdict(false);
     }
     response.bodyOutputStream.write(blob, blob.length);
   });
 
   gHttpServer.start(4444);
 });
@@ -289,23 +290,27 @@ function promiseQueryReputation(query, e
     do_check_eq(Cr.NS_OK, aStatus);
     do_check_eq(aShouldBlock, expectedShouldBlock);
     deferred.resolve(true);
   }
   gAppRep.queryReputation(query, onComplete);
   return deferred.promise;
 }
 
+add_task(function()
+{
+  // Wait for Safebrowsing local list updates to complete.
+  yield waitForUpdates();
+});
+
 add_task(function test_signature_whitelists()
 {
   // We should never get to the remote server.
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/throw");
-  // Wait for Safebrowsing local list updates to complete.
-  yield waitForUpdates();
 
   // Use BackgroundFileSaver to extract the signature on Windows.
   let destFile = getTempFile(TEST_FILE_NAME_1);
 
   let data = readFileToString("data/signed_win.exe");
   let saver = new BackgroundFileSaverOutputStream();
   let completionPromise = promiseSaverComplete(saver);
   saver.enableSignatureInfo();
@@ -320,12 +325,44 @@ add_task(function test_signature_whiteli
 
   // evil.com is not on the allowlist, but this binary is signed by an entity
   // whose certificate information is on the allowlist.
   yield promiseQueryReputation({sourceURI: createURI("http://evil.com"),
                                 signatureInfo: saver.signatureInfo,
                                 fileSize: 12}, false);
 });
 
+add_task(function test_blocked_binary()
+{
+  // We should reach the remote server for a verdict.
+  Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
+                             "http://localhost:4444/download");
+  // evil.com should return a malware verdict from the remote server.
+  yield promiseQueryReputation({sourceURI: createURI("http://evil.com"),
+                                suggestedFileName: "noop.bat",
+                                fileSize: 12}, true);
+});
+
+add_task(function test_non_binary()
+{
+  // We should not reach the remote server for a verdict for non-binary files.
+  Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
+                             "http://localhost:4444/throw");
+  yield promiseQueryReputation({sourceURI: createURI("http://evil.com"),
+                                suggestedFileName: "noop.txt",
+                                fileSize: 12}, false);
+});
+
+add_task(function test_good_binary()
+{
+  // We should reach the remote server for a verdict.
+  Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
+                             "http://localhost:4444/download");
+  // mozilla.com should return a not-guilty verdict from the remote server.
+  yield promiseQueryReputation({sourceURI: createURI("http://mozilla.com"),
+                                suggestedFileName: "noop.bat",
+                                fileSize: 12}, false);
+});
+
 add_task(function test_teardown()
 {
   gStillRunning = false;
 });
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -844,18 +844,16 @@ NavHistoryObserver.prototype = {
  * Generic nsINavHistoryResultObserver that doesn't implement anything, but
  * provides dummy methods to prevent errors about an object not having a certain
  * method.
  */
 function NavHistoryResultObserver() {}
 
 NavHistoryResultObserver.prototype = {
   batching: function () {},
-  containerClosed: function () {},
-  containerOpened: function () {},
   containerStateChanged: function () {},
   invalidateContainer: function () {},
   nodeAnnotationChanged: function () {},
   nodeDateAddedChanged: function () {},
   nodeHistoryDetailsChanged: function () {},
   nodeIconChanged: function () {},
   nodeInserted: function () {},
   nodeKeywordChanged: function () {},
--- a/toolkit/components/places/tests/unit/test_nsINavHistoryViewer.js
+++ b/toolkit/components/places/tests/unit/test_nsINavHistoryViewer.js
@@ -92,17 +92,16 @@ add_test(function check_history_query() 
   options.sortingMode = options.SORT_BY_DATE_DESCENDING;
   options.resultType = options.RESULTS_AS_VISIT;
   var query = histsvc.getNewQuery();
   var result = histsvc.executeQuery(query, options);
   result.addObserver(resultObserver, false);
   var root = result.root;
   root.containerOpen = true;
 
-  // nsINavHistoryResultObserver.containerOpened
   do_check_neq(resultObserver.openedContainer, null);
 
   // nsINavHistoryResultObserver.nodeInserted
   // add a visit
   promiseAddVisits(testURI).then(function() {
     do_check_eq(testURI.spec, resultObserver.insertedNode.uri);
 
     // nsINavHistoryResultObserver.nodeHistoryDetailsChanged
@@ -143,17 +142,16 @@ add_test(function check_history_query() 
         do_check_false(resultObserver.inBatchMode);
         bmsvc.runInBatchMode({
           runBatched: function (aUserData) {
             do_check_true(resultObserver.inBatchMode);
           }
         }, null);
         do_check_false(resultObserver.inBatchMode);
 
-        // nsINavHistoryResultObserver.containerClosed
         root.containerOpen = false;
         do_check_eq(resultObserver.closedContainer, resultObserver.openedContainer);
         result.removeObserver(resultObserver);
         resultObserver.reset();
         promiseAsyncUpdates().then(run_next_test);
       });
     });
   });
@@ -163,17 +161,16 @@ add_test(function check_bookmarks_query(
   var options = histsvc.getNewQueryOptions();
   var query = histsvc.getNewQuery();
   query.setFolders([bmsvc.bookmarksMenuFolder], 1);
   var result = histsvc.executeQuery(query, options);
   result.addObserver(resultObserver, false);
   var root = result.root;
   root.containerOpen = true;
 
-  // nsINavHistoryResultObserver.containerOpened
   do_check_neq(resultObserver.openedContainer, null);
 
   // nsINavHistoryResultObserver.nodeInserted
   // add a bookmark
   var testBookmark = bmsvc.insertBookmark(bmsvc.bookmarksMenuFolder, testURI, bmsvc.DEFAULT_INDEX, "foo");
   do_check_eq("foo", resultObserver.insertedNode.title);
   do_check_eq(testURI.spec, resultObserver.insertedNode.uri);
 
@@ -212,34 +209,32 @@ add_test(function check_bookmarks_query(
   do_check_false(resultObserver.inBatchMode);
   bmsvc.runInBatchMode({
     runBatched: function (aUserData) {
       do_check_true(resultObserver.inBatchMode);
     }
   }, null);
   do_check_false(resultObserver.inBatchMode);
 
-  // nsINavHistoryResultObserver.containerClosed
   root.containerOpen = false;
   do_check_eq(resultObserver.closedContainer, resultObserver.openedContainer);
   result.removeObserver(resultObserver);
   resultObserver.reset();
   promiseAsyncUpdates().then(run_next_test);
 });
 
 add_test(function check_mixed_query() {
   var options = histsvc.getNewQueryOptions();
   var query = histsvc.getNewQuery();
   query.onlyBookmarked = true;
   var result = histsvc.executeQuery(query, options);
   result.addObserver(resultObserver, false);
   var root = result.root;
   root.containerOpen = true;
 
-  // nsINavHistoryResultObserver.containerOpened
   do_check_neq(resultObserver.openedContainer, null);
 
   // nsINavHistoryResultObserver.batching
   do_check_false(resultObserver.inBatchMode);
   histsvc.runInBatchMode({
     runBatched: function (aUserData) {
       do_check_true(resultObserver.inBatchMode);
     }
@@ -247,15 +242,14 @@ add_test(function check_mixed_query() {
   do_check_false(resultObserver.inBatchMode);
   bmsvc.runInBatchMode({
     runBatched: function (aUserData) {
       do_check_true(resultObserver.inBatchMode);
     }
   }, null);
   do_check_false(resultObserver.inBatchMode);
 
-  // nsINavHistoryResultObserver.containerClosed
   root.containerOpen = false;
   do_check_eq(resultObserver.closedContainer, resultObserver.openedContainer);
   result.removeObserver(resultObserver);
   resultObserver.reset();
   promiseAsyncUpdates().then(run_next_test);
 });
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -1805,16 +1805,25 @@ Engine.prototype = {
           // Ignore failure
           LOG("_parseURL: Url element has an invalid param");
         }
       } else if (param.localName == "MozParam" &&
                  // We only support MozParams for default search engines
                  this._isDefault) {
         var value;
         let condition = param.getAttribute("condition");
+
+        // MozParams must have a condition to be valid
+        if (!condition) {
+          let engineLoc = this._location;
+          let paramName = param.getAttribute("name");
+          LOG("_parseURL: MozParam (" + paramName + ") without a condition attribute found parsing engine: " + engineLoc);
+          continue;
+        }
+
         switch (condition) {
           case "purpose":
             url.addParam(param.getAttribute("name"),
                          param.getAttribute("value"),
                          param.getAttribute("purpose"));
             // _addMozParam is not needed here since it can be serialized fine without. _addMozParam
             // also requires a unique "name" which is not normally the case when @purpose is used.
             break;
--- a/toolkit/devtools/apps/app-actor-front.js
+++ b/toolkit/devtools/apps/app-actor-front.js
@@ -134,19 +134,17 @@ function uploadPackage(client, webappsAc
   return deferred.promise;
 }
 
 function removeServerTemporaryFile(client, fileActor) {
   let request = {
     to: fileActor,
     type: "remove"
   };
-  client.request(request, function (aResponse) {
-    console.error("Failed removing server side temporary package file", aResponse);
-  });
+  client.request(request);
 }
 
 function installPackaged(client, webappsActor, packagePath, appId) {
   let deferred = promise.defer();
   let file = FileUtils.File(packagePath);
   let packagePromise;
   if (file.isDirectory()) {
     let tmpZipFile = FileUtils.getDir("TmpD", [], true);
@@ -178,17 +176,19 @@ function installPackaged(client, webapps
               deferred.resolve({appId: res.appId});
           });
           // Ensure deleting the temporary package file, but only if that a temporary
           // package created when we pass a directory as `packagePath`
           if (zipFile != file)
             zipFile.remove(false);
           // In case of success or error, ensure deleting the temporary package file
           // also created on the device, but only once install request is done
-          deferred.promise.then(removeServerTemporaryFile, removeServerTemporaryFile);
+          deferred.promise.then(
+            () => removeServerTemporaryFile(client, fileActor),
+            () => removeServerTemporaryFile(client, fileActor));
         });
   });
   return deferred.promise;
 }
 exports.installPackaged = installPackaged;
 
 function installHosted(client, webappsActor, appId, metadata, manifest) {
   let deferred = promise.defer();
--- a/toolkit/devtools/apps/tests/test_webapps_actor.html
+++ b/toolkit/devtools/apps/tests/test_webapps_actor.html
@@ -16,18 +16,16 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script class="testbody" type="application/javascript;version=1.8">
 
 "use strict";
 
-var launchableValue = undefined;
-
 var index = -1;
 
 function debug(aMsg) {
   //dump("== Tests debug == " + aMsg + "\n");
 }
 
 function next() {
   index += 1;
@@ -42,17 +40,16 @@ function next() {
   }
 }
 
 function start() {
   next();
 }
 
 function finish() {
-  SpecialPowers.setAllAppsLaunchable(launchableValue);
   SpecialPowers.removePermission("webapps-manage", document);
   SimpleTest.finish();
 }
 
 function cbError(aError) {
   ok(false, "Error callback invoked " + aError);
   finish();
 }
@@ -68,17 +65,17 @@ const PACKAGED_APP_MANIFEST = PACKAGED_A
 const CERTIFIED_APP_ID = "test-certified-id";
 const CERTIFIED_APP_ORIGIN = "app://" + CERTIFIED_APP_ID;
 const CERTIFIED_APP_MANIFEST = CERTIFIED_APP_ORIGIN + "/manifest.webapp";
 
 var steps = [
   function() {
     info("== SETUP ==");
     // Set up
-    launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.addPermission("webapps-manage", true, document);
     SpecialPowers.addPermission("browser", true, document);
     SpecialPowers.addPermission("embed-apps", true, document);
 
     // Required on firefox as these prefs are only set on b2g:
     SpecialPowers.pushPrefEnv({
       set: [["dom.mozBrowserFramesEnabled", true],
             ["security.apps.privileged.CSP.default",
--- a/toolkit/webapps/WebappOSUtils.jsm
+++ b/toolkit/webapps/WebappOSUtils.jsm
@@ -1,20 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const CC = Components.Constructor;
-const Cu = Components.utils;
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu, Constructor: CC } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
 
 this.EXPORTED_SYMBOLS = ["WebappOSUtils"];
 
 // Returns the MD5 hash of a string.
 function computeHash(aString) {
   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
                   createInstance(Ci.nsIScriptableUnicodeConverter);
   converter.charset = "UTF-8";
@@ -38,75 +36,93 @@ function computeHash(aString) {
 }
 
 this.WebappOSUtils = {
   getUniqueName: function(aApp) {
     return this.sanitizeStringForFilename(aApp.name).toLowerCase() + "-" +
            computeHash(aApp.manifestURL);
   },
 
+#ifdef XP_WIN
+  /**
+   * Returns the registry key associated to the given app and a boolean that
+   * specifies whether we're using the old naming scheme or the new one.
+   */
+  getAppRegKey: function(aApp) {
+    let regKey = Cc["@mozilla.org/windows-registry-key;1"].
+                 createInstance(Ci.nsIWindowsRegKey);
+
+    try {
+      regKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                  "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" +
+                  this.getUniqueName(aApp), Ci.nsIWindowsRegKey.ACCESS_READ);
+
+      return { value: regKey,
+               namingSchemeVersion: 2};
+    } catch (ex) {}
+
+    // Fall back to the old installation naming scheme
+    try {
+      regKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                  "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" +
+                  aApp.origin, Ci.nsIWindowsRegKey.ACCESS_READ);
+
+      return { value: regKey,
+               namingSchemeVersion: 1 };
+    } catch (ex) {}
+
+    return null;
+  },
+#endif
+
   /**
    * Returns the executable of the given app, identifying it by its unique name,
    * which is in either the new format or the old format.
    * On Mac OS X, it returns the identifier of the app.
    *
    * The new format ensures a readable and unique name for an app by combining
    * its name with a hash of its manifest URL.  The old format uses its origin,
    * which is only unique until we support multiple apps per origin.
    */
   getLaunchTarget: function(aApp) {
-    let uniqueName = this.getUniqueName(aApp);
+#ifdef XP_WIN
+    let appRegKey = this.getAppRegKey(aApp);
 
-#ifdef XP_WIN
-    let isOldNamingScheme = false;
-    let appRegKey;
-    try {
-      let open = CC("@mozilla.org/windows-registry-key;1",
-                    "nsIWindowsRegKey", "open");
-      appRegKey = open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
-                       "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" +
-                       uniqueName, Ci.nsIWindowsRegKey.ACCESS_READ);
-    } catch (ex) {
-      // Fall back to the old installation naming scheme
-      try {
-        appRegKey = open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
-                         "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" +
-                         aApp.origin, Ci.nsIWindowsRegKey.ACCESS_READ);
-        isOldNamingScheme = true;
-      } catch (ex) {
-        return null;
-      }
+    if (!appRegKey) {
+      return null;
     }
 
     let appFilename, installLocation;
     try {
-      appFilename = appRegKey.readStringValue("AppFilename");
-      installLocation = appRegKey.readStringValue("InstallLocation");
+      appFilename = appRegKey.value.readStringValue("AppFilename");
+      installLocation = appRegKey.value.readStringValue("InstallLocation");
     } catch (ex) {
       return null;
     } finally {
-      appRegKey.close();
+      appRegKey.value.close();
     }
 
     installLocation = installLocation.substring(1, installLocation.length - 1);
 
-    if (isOldNamingScheme &&
+    if (appRegKey.namingSchemeVersion == 1 &&
         !this.isOldInstallPathValid(aApp, installLocation)) {
       return null;
     }
 
     let initWithPath = CC("@mozilla.org/file/local;1",
                           "nsILocalFile", "initWithPath");
     let launchTarget = initWithPath(installLocation);
     launchTarget.append(appFilename + ".exe");
 
     return launchTarget;
 #elifdef XP_MACOSX
-    let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"]
-                     .createInstance(Ci.nsIMacWebAppUtils);
+    let uniqueName = this.getUniqueName(aApp);
+
+    let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"].
+                   createInstance(Ci.nsIMacWebAppUtils);
 
     try {
       let path;
       if (path = mwaUtils.pathForAppWithIdentifier(uniqueName)) {
         return [ uniqueName, path ];
       }
     } catch(ex) {}
 
@@ -116,16 +132,18 @@ this.WebappOSUtils = {
       if ((path = mwaUtils.pathForAppWithIdentifier(aApp.origin)) &&
            this.isOldInstallPathValid(aApp, path)) {
         return [ aApp.origin, path ];
       }
     } catch(ex) {}
 
     return [ null, null ];
 #elifdef XP_UNIX
+    let uniqueName = this.getUniqueName(aApp);
+
     let exeFile = Services.dirsvc.get("Home", Ci.nsIFile);
     exeFile.append("." + uniqueName);
     exeFile.append("webapprt-stub");
 
     // Fall back to the old installation naming scheme
     if (!exeFile.exists()) {
       exeFile = Services.dirsvc.get("Home", Ci.nsIFile);
 
@@ -217,40 +235,40 @@ this.WebappOSUtils = {
 
     return packagePath;
   },
 
   launch: function(aApp) {
     let uniqueName = this.getUniqueName(aApp);
 
 #ifdef XP_WIN
-    let initProcess = CC("@mozilla.org/process/util;1",
-                         "nsIProcess", "init");
-
     let launchTarget = this.getLaunchTarget(aApp);
     if (!launchTarget) {
       return false;
     }
 
     try {
-      let process = initProcess(launchTarget);
+      let process = Cc["@mozilla.org/process/util;1"].
+                    createInstance(Ci.nsIProcess);
+
+      process.init(launchTarget);
       process.runwAsync([], 0);
     } catch (e) {
       return false;
     }
 
     return true;
 #elifdef XP_MACOSX
     let [ launchIdentifier, path ] = this.getLaunchTarget(aApp);
     if (!launchIdentifier) {
       return false;
     }
 
-    let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"]
-                     .createInstance(Ci.nsIMacWebAppUtils);
+    let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"].
+                   createInstance(Ci.nsIMacWebAppUtils);
 
     try {
       mwaUtils.launchAppWithIdentifier(launchIdentifier);
     } catch(e) {
       return false;
     }
 
     return true;
@@ -270,37 +288,95 @@ this.WebappOSUtils = {
       return false;
     }
 
     return true;
 #endif
   },
 
   uninstall: function(aApp) {
-    let uniqueName = this.getUniqueName(aApp);
+#ifdef XP_WIN
+    let appRegKey = this.getAppRegKey(aApp);
+
+    if (!appRegKey) {
+      return Promise.reject("App registry key not found");
+    }
+
+    let deferred = Promise.defer();
+
+    try {
+      let uninstallerPath = appRegKey.value.readStringValue("UninstallString");
+      uninstallerPath = uninstallerPath.substring(1, uninstallerPath.length - 1);
+
+      let uninstaller = Cc["@mozilla.org/file/local;1"].
+                        createInstance(Ci.nsIFile);
+      uninstaller.initWithPath(uninstallerPath);
 
-#ifdef XP_UNIX
-#ifndef XP_MACOSX
+      let process = Cc["@mozilla.org/process/util;1"].
+                    createInstance(Ci.nsIProcess);
+      process.init(uninstaller);
+      process.runwAsync(["/S"], 1, (aSubject, aTopic) => {
+        if (aTopic == "process-finished") {
+          deferred.resolve(true);
+        } else {
+          deferred.reject("Uninstaller failed with exit code: " + aSubject.exitValue);
+        }
+      });
+    } catch (e) {
+      deferred.reject(e);
+    } finally {
+      appRegKey.value.close();
+    }
+
+    return deferred.promise;
+#elifdef XP_MACOSX
+    let [ , path ] = this.getLaunchTarget(aApp);
+    if (!path) {
+      return Promise.reject("App not found");
+    }
+
+    let deferred = Promise.defer();
+
+    let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"].
+                   createInstance(Ci.nsIMacWebAppUtils);
+
+    mwaUtils.trashApp(path, (aResult) => {
+      if (aResult == Cr.NS_OK) {
+        deferred.resolve(true);
+      } else {
+        deferred.resolve("Error moving the app to the Trash: " + aResult);
+      }
+    });
+
+    return deferred.promise;
+#elifdef XP_UNIX
     let exeFile = this.getLaunchTarget(aApp);
     if (!exeFile) {
-      return false;
+      return Promise.reject("App executable file not found");
     }
 
+    let deferred = Promise.defer();
+
     try {
       let process = Cc["@mozilla.org/process/util;1"]
                       .createInstance(Ci.nsIProcess);
 
       process.init(exeFile);
-      process.runAsync(["-remove"], 1);
+      process.runAsync(["-remove"], 1, (aSubject, aTopic) => {
+        if (aTopic == "process-finished") {
+          deferred.resolve(true);
+        } else {
+          deferred.reject("Uninstaller failed with exit code: " + aSubject.exitValue);
+        }
+      });
     } catch (e) {
-      return false;
+      deferred.reject(e);
     }
 
-    return true;
-#endif
+    return deferred.promise;
 #endif
   },
 
   /**
    * Returns true if the given install path (in the old naming scheme) actually
    * belongs to the given application.
    */
   isOldInstallPathValid: function(aApp, aInstallPath) {
--- a/webapprt/win/webapp-uninstaller.nsi.in
+++ b/webapprt/win/webapp-uninstaller.nsi.in
@@ -3,20 +3,17 @@
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # Required Plugins:
 # ShellLink   http://nsis.sourceforge.net/ShellLink_plug-in
 
 ; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
 !verbose 3
 
-; 7-Zip provides better compression than the lzma from NSIS so we add the files
-; uncompressed and let the application installer compress it.
 SetDatablockOptimize on
-SetCompress off
 CRCCheck on
 SilentInstall silent
 
 RequestExecutionLevel user
 
 !addplugindir ./
 
 ; prevents compiling of the reg write logging.
@@ -65,59 +62,67 @@ Name "Mozilla Web App Runtime App"
 OutFile "${UninstallerFilename}"
 ShowUnInstDetails nevershow
 
 # Create a blank page so that the default pages (instfiles) don't appear
 UninstPage custom un.blankPage
 
 ################################################################################
 # Install Sections
-# The "installer" that is generated by this file will be run during the build
-# process to generate an uninstaller.  We call `WriteUninstaller` during
-# `onInit` so this section is empty.
+# The "installer" that is generated by this file is a stub that generates the
+# uninstaller at runtime in a temp directory and launches it.
+# We call `WriteUninstaller` during `onInit` so this section is empty.
 Section ""
 SectionEnd
 
 ################################################################################
 # This is where uninstallation happens
 ################################################################################
-Function un.blankPage
-  MessageBox MB_OKCANCEL "$(UN_CONFIRM_UNINSTALL)" /SD IDOK IDCANCEL done
-
+Function un.webappUninstall
   ; Delete the app exe to prevent launching the app while we are uninstalling.
   ClearErrors
   ${DeleteFile} "$INSTDIR\${FileMainEXE}"
   ${If} ${Errors}
     ; If the app is running, rename the EXE out of the way
     CreateDirectory "$AppRTTempDir"
     Rename "$INSTDIR\${FileMainEXE}" "$AppRTTempDir\${FileMainEXE}"
     ClearErrors
   ${EndIf}
 
-
   SetShellVarContext current  ; Set SHCTX to HKCU
 
   ; Remove our entry in the "Uninstall" key
   ${un.RegCleanUninstall}
 
   ; Remove our shortcuts from start menu, desktop, and taskbar
   ${un.DeleteShortcuts}
 
   ; Parse the uninstall log to remove all installed
   ; files / directories this install is responsible for.
   ${un.ParseUninstallLog}
 
-  ; Remove the uninstall directory that we control
-  RmDir /r "$INSTDIR\uninstall"
+  ; Remove the uninstall directory that we control.
+  ; The installer is in the uninstall directory, it generates
+  ; the uninstaller in a temp directory and waits for its
+  ; execution. Thus, we can't remove the uninstall directory
+  ; now and need to wait for a restart.
+  ; See bug 994965.
+  RmDir /r /REBOOTOK "$INSTDIR\uninstall"
 
   ; Remove the installation directory if it is empty
   ${RemoveDir} "$INSTDIR"
 
   ; Refresh shell icons to reflect the changes we've made
   ${un.RefreshShellIcons}
+FunctionEnd
+
+Function un.blankPage
+  MessageBox MB_OKCANCEL "$(UN_CONFIRM_UNINSTALL)" /SD IDOK IDCANCEL done
+
+  Call un.webappUninstall
 
   done:
 FunctionEnd
 
 ################################################################################
 # Language
 
 !verbose push
@@ -139,18 +144,18 @@ Function .onInit
   Delete "$0"
   CreateDirectory "$0"
   SetOutPath "$0"
 
   StrCpy $1 "$0\${UninstallerFilename}"
   WriteUninstaller "$1"
 
   ${GetParameters} $2
-  StrCpy $2 "_?=$EXEDIR $2"
-  Exec '"$1" $2'
+  StrCpy $2 "$2 _?=$EXEDIR"
+  ExecWait '"$1" $2'
   Quit
 FunctionEnd
 
 Function un.onInit
   ; Remove the current exe directory from the search order.
   ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
   System::Call 'kernel32::SetDllDirectoryW(w "")'
 
@@ -163,9 +168,13 @@ Function un.onInit
   ReadINIStr $AppName "$INSTDIR\webapp.ini" "Webapp" "Name"
 
   ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}"
     Abort
   ${EndUnless}
 
   StrCpy $AppRTTempDir "$TEMP\moz_webapprt"
   RmDir /r "$AppRTTempDir"
+
+  ${If} ${Silent}
+    Call un.webappUninstall
+  ${EndIf}
 FunctionEnd
--- a/widget/cocoa/nsMacWebAppUtils.mm
+++ b/widget/cocoa/nsMacWebAppUtils.mm
@@ -52,8 +52,31 @@ NS_IMETHODIMP nsMacWebAppUtils::LaunchAp
                         options: (NSWorkspaceLaunchOptions)0
                         additionalEventParamDescriptor: nil
                         launchIdentifier: NULL];
 
   return success ? NS_OK : NS_ERROR_FAILURE;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
+
+NS_IMETHODIMP nsMacWebAppUtils::TrashApp(const nsAString& path, nsITrashAppCallback* aCallback) {
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  if (NS_WARN_IF(!aCallback)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsCOMPtr<nsITrashAppCallback> callback = aCallback;
+
+  NSString* tempString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(((nsString)path).get())
+                                   length:path.Length()];
+
+  [[NSWorkspace sharedWorkspace] recycleURLs: [NSArray arrayWithObject:[NSURL fileURLWithPath:tempString]]
+    completionHandler: ^(NSDictionary *newURLs, NSError *error) {
+      nsresult rv = (error == nil) ? NS_OK : NS_ERROR_FAILURE;
+      callback->TrashAppFinished(rv);
+    }];
+
+  return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
--- a/widget/nsIMacWebAppUtils.idl
+++ b/widget/nsIMacWebAppUtils.idl
@@ -1,25 +1,35 @@
 /* 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"
 
 interface nsIMacWebAppUtils;
 
+[scriptable, function, uuid(8c899c4f-58c1-4b74-9034-3bb64e484b68)]
+interface nsITrashAppCallback : nsISupports
+{
+  void trashAppFinished(in nsresult rv);
+};
+
 /**
  * Allow MozApps API to locate and manipulate natively installed apps
  */
 
-[scriptable, uuid(e9096367-ddd9-45e4-b762-49c0c18b7119)]
+[scriptable, uuid(c69cf343-ea41-428b-b161-4655fd54d8e7)]
 interface nsIMacWebAppUtils : nsISupports {
   /**
    * Find the path for an app with the given signature.
    */
   AString pathForAppWithIdentifier(in AString bundleIdentifier);
 
   /**
    * Launch the app with the given identifier, if it exists.
    */
   void launchAppWithIdentifier(in AString bundleIdentifier);
 
+  /**
+   * Move the app from the given directory to the Trash.
+   */
+  void trashApp(in AString path, in nsITrashAppCallback callback);
 };
--- a/xpcom/glue/tests/gtest/moz.build
+++ b/xpcom/glue/tests/gtest/moz.build
@@ -8,13 +8,15 @@ UNIFIED_SOURCES += [
     'TestFileUtils.cpp',
     'TestGCPostBarriers.cpp',
 ]
 
 LOCAL_INCLUDES = [
     '../..',
 ]
 
+FAIL_ON_WARNINGS = True
+
 LIBRARY_NAME = 'xpcom_glue_gtest'
 
 EXPORT_LIBRARY = True
 
 FINAL_LIBRARY = 'xul-gtest'