Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 08 Oct 2015 14:29:04 -0700
changeset 266868 46da59584acb32e4189f3b35f14b03ca20d9c727
parent 266828 28a0a90ea90a3517bcf38fe362468ac7feb3c94b (current diff)
parent 266867 ca546bb0c669ec55f6264c80c86992d0fc18f10b (diff)
child 266894 e5f1bc63ad52d0eb86f7fb838226ca6036774660
push id29499
push userkwierso@gmail.com
push dateThu, 08 Oct 2015 21:29:10 +0000
treeherdermozilla-central@46da59584acb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone44.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 central, a=merge
testing/web-platform/meta/content-security-policy/blink-contrib/shared-worker-connect-src-blocked.sub.html.ini
testing/web-platform/mozilla/meta/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html.ini
--- a/accessible/generic/ARIAGridAccessible.cpp
+++ b/accessible/generic/ARIAGridAccessible.cpp
@@ -542,18 +542,19 @@ ARIARowAccessible::
 }
 
 NS_IMPL_ISUPPORTS_INHERITED0(ARIARowAccessible, Accessible)
 
 GroupPos
 ARIARowAccessible::GroupPosition()
 {
   int32_t count = 0, index = 0;
-  if (nsCoreUtils::GetUIntAttr(nsAccUtils::TableFor(this)->GetContent(),
-                               nsGkAtoms::aria_rowcount, &count) &&
+  Accessible* table = nsAccUtils::TableFor(this);
+  if (table && nsCoreUtils::GetUIntAttr(table->GetContent(),
+                                        nsGkAtoms::aria_rowcount, &count) &&
       nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) {
     return GroupPos(0, index, count);
   }
 
   return AccessibleWrap::GroupPosition();
 }
 
 
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -1,10 +1,9 @@
 [DEFAULT]
-skip-if = os == 'android' || buildapp == 'b2g' || os == 'mac'
 support-files =
   head.js
   context.html
   ctxmenu-image.png
 
 [browser_ext_simple.js]
 [browser_ext_currentWindow.js]
 [browser_ext_browserAction_simple.js]
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -98,24 +98,25 @@ SEARCH_PATHS = [
     'testing/marionette/transport',
     'testing/marionette/driver',
     'testing/luciddream',
     'testing/mozbase/mozcrash',
     'testing/mozbase/mozdebug',
     'testing/mozbase/mozdevice',
     'testing/mozbase/mozfile',
     'testing/mozbase/mozhttpd',
+    'testing/mozbase/mozinfo',
+    'testing/mozbase/mozinstall',
     'testing/mozbase/mozleak',
     'testing/mozbase/mozlog',
     'testing/mozbase/moznetwork',
     'testing/mozbase/mozprocess',
     'testing/mozbase/mozprofile',
     'testing/mozbase/mozrunner',
     'testing/mozbase/mozsystemmonitor',
-    'testing/mozbase/mozinfo',
     'testing/mozbase/mozscreenshot',
     'testing/mozbase/moztest',
     'testing/mozbase/mozversion',
     'testing/mozbase/manifestparser',
     'xpcom/idl-parser',
 ]
 
 # Individual files providing mach commands.
--- a/docshell/test/unit/test_nsDefaultURIFixup_info.js
+++ b/docshell/test/unit/test_nsDefaultURIFixup_info.js
@@ -207,21 +207,20 @@ var testcases = [ {
     fixedURI: "http://[64:ff9b::8.8.8.8]/~moz",
     protocolChange: true
   }, {
     input: "[::1][::1]",
     keywordLookup: true,
     protocolChange: true
   }, {
     input: "[::1][100",
-    fixedURI: "http://[::1][100/",
-    alternateURI: "http://[::1][100/",
+    fixedURI: null,
+    alternateURI: null,
     keywordLookup: true,
-    protocolChange: true,
-    affectedByDNSForSingleHosts: true,
+    protocolChange: true
   }, {
     input: "[::1]]",
     keywordLookup: true,
     protocolChange: true
   }, {
     input: "1234",
     fixedURI: "http://1234/",
     alternateURI: "http://www.1234.com/",
@@ -498,23 +497,24 @@ if (Services.appinfo.OS.toLowerCase().st
     fixedURI: "file:///some/file.txt",
     protocolChange: true,
   });
   testcases.push({
     input: "//mozilla",
     fixedURI: "file:////mozilla",
     protocolChange: true,
   });
+  // \ is an invalid character in the hostname until bug 652186 is implemented
   testcases.push({
     input: "mozilla\\",
-    fixedURI: "http://mozilla\\/",
-    alternateURI: "http://www.mozilla/",
+    // fixedURI: "http://mozilla\\/",
+    // alternateURI: "http://www.mozilla/",
     keywordLookup: true,
     protocolChange: true,
-    affectedByDNSForSingleHosts: true,
+    // affectedByDNSForSingleHosts: true,
   });
 }
 
 function sanitize(input) {
   return input.replace(/\r|\n/g, "").trim();
 }
 
 
--- a/dom/media/MP3Demuxer.cpp
+++ b/dom/media/MP3Demuxer.cpp
@@ -79,21 +79,20 @@ MP3Demuxer::GetTrackDemuxer(TrackInfo::T
 }
 
 bool
 MP3Demuxer::IsSeekable() const {
   return true;
 }
 
 void
-MP3Demuxer::NotifyDataArrived(uint32_t aLength, int64_t aOffset) {
+MP3Demuxer::NotifyDataArrived() {
   // TODO: bug 1169485.
   NS_WARNING("Unimplemented function NotifyDataArrived");
-  MP3DEMUXER_LOGV("NotifyDataArrived(%u, %" PRId64 ") mOffset=%" PRId64,
-                  aLength, aOffset, mTrackDemuxer->GetResourceOffset());
+  MP3DEMUXER_LOGV("NotifyDataArrived()");
 }
 
 void
 MP3Demuxer::NotifyDataRemoved() {
   // TODO: bug 1169485.
   NS_WARNING("Unimplemented function NotifyDataRemoved");
   MP3DEMUXER_LOGV("NotifyDataRemoved()");
 }
--- a/dom/media/MP3Demuxer.h
+++ b/dom/media/MP3Demuxer.h
@@ -20,17 +20,17 @@ public:
   // MediaDataDemuxer interface.
   explicit MP3Demuxer(MediaResource* aSource);
   nsRefPtr<InitPromise> Init() override;
   bool HasTrackType(TrackInfo::TrackType aType) const override;
   uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override;
   already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(
       TrackInfo::TrackType aType, uint32_t aTrackNumber) override;
   bool IsSeekable() const override;
-  void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override;
+  void NotifyDataArrived() override;
   void NotifyDataRemoved() override;
   // Do not shift the calculated buffered range by the start time of the first
   // decoded frame. The mac MP3 decoder will buffer some samples and the first
   // frame returned has typically a start time that is non-zero, causing our
   // buffered range to have a negative start time.
   bool ShouldComputeStartTime() const override { return false; }
 
 private:
--- a/dom/media/MediaDataDemuxer.h
+++ b/dom/media/MediaDataDemuxer.h
@@ -72,25 +72,27 @@ public:
 
   // Returns the media's crypto information, or nullptr if media isn't
   // encrypted.
   virtual UniquePtr<EncryptionInfo> GetCrypto()
   {
     return nullptr;
   }
 
-  // Notifies the demuxer that the underlying resource has received more data.
+  // Notifies the demuxer that the underlying resource has received more data
+  // since the demuxer was initialized.
   // The demuxer can use this mechanism to inform all track demuxers that new
-  // data is available.
-  virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset) { }
+  // data is available and to refresh its buffered range.
+  virtual void NotifyDataArrived() { }
 
-  // Notifies the demuxer that the underlying resource has had data removed.
+  // Notifies the demuxer that the underlying resource has had data removed
+  // since the demuxer was initialized.
   // The demuxer can use this mechanism to inform all track demuxers to update
-  // its TimeIntervals.
-  // This will be called should the demuxer be used with MediaSource.
+  // its buffered range.
+  // This will be called should the demuxer be used with MediaSourceResource.
   virtual void NotifyDataRemoved() { }
 
   // Indicate to MediaFormatReader if it should compute the start time
   // of the demuxed data. If true (default) the first sample returned will be
   // used as reference time base.
   virtual bool ShouldComputeStartTime() const { return true; }
 
 protected:
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1598,17 +1598,17 @@ MediaFormatReader::NotifyDemuxer(uint32_
 
   LOGV("aLength=%u, aOffset=%lld", aLength, aOffset);
   if (mShutdown || !mDemuxer ||
       (!mDemuxerInitDone && !mDemuxerInitRequest.Exists())) {
     return;
   }
 
   if (aLength || aOffset) {
-    mDemuxer->NotifyDataArrived(aLength, aOffset);
+    mDemuxer->NotifyDataArrived();
   } else {
     mDemuxer->NotifyDataRemoved();
   }
   if (!mInitDone) {
     return;
   }
   if (HasVideo()) {
     mVideo.mReceivedNewData = true;
--- a/dom/media/fmp4/MP4Demuxer.cpp
+++ b/dom/media/fmp4/MP4Demuxer.cpp
@@ -181,17 +181,17 @@ MP4Demuxer::GetTrackDemuxer(TrackInfo::T
 
 bool
 MP4Demuxer::IsSeekable() const
 {
   return mMetadata->CanSeek();
 }
 
 void
-MP4Demuxer::NotifyDataArrived(uint32_t aLength, int64_t aOffset)
+MP4Demuxer::NotifyDataArrived()
 {
   for (uint32_t i = 0; i < mDemuxers.Length(); i++) {
     mDemuxers[i]->NotifyDataArrived();
   }
 }
 
 void
 MP4Demuxer::NotifyDataRemoved()
--- a/dom/media/fmp4/MP4Demuxer.h
+++ b/dom/media/fmp4/MP4Demuxer.h
@@ -35,17 +35,17 @@ public:
 
   virtual already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(TrackInfo::TrackType aType,
                                                               uint32_t aTrackNumber) override;
 
   virtual bool IsSeekable() const override;
 
   virtual UniquePtr<EncryptionInfo> GetCrypto() override;
 
-  virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override;
+  virtual void NotifyDataArrived() override;
 
   virtual void NotifyDataRemoved() override;
 
 private:
   friend class MP4TrackDemuxer;
   nsRefPtr<MediaResource> mResource;
   nsRefPtr<mp4_demuxer::ResourceStream> mStream;
   nsRefPtr<MediaByteBuffer> mInitData;
--- a/dom/media/mediasource/MediaSourceDemuxer.cpp
+++ b/dom/media/mediasource/MediaSourceDemuxer.cpp
@@ -48,17 +48,17 @@ MediaSourceDemuxer::AttemptInit()
     return InitPromise::CreateAndResolve(NS_OK, __func__);
   }
 
   nsRefPtr<InitPromise> p = mInitPromise.Ensure(__func__);
 
   return p;
 }
 
-void MediaSourceDemuxer::NotifyDataArrived(uint32_t aLength, int64_t aOffset)
+void MediaSourceDemuxer::NotifyDataArrived()
 {
   nsRefPtr<MediaSourceDemuxer> self = this;
   nsCOMPtr<nsIRunnable> task =
     NS_NewRunnableFunction([self] () {
       if (self->mInitPromise.IsEmpty()) {
         return;
       }
       if (self->ScanSourceBuffersForContent()) {
--- a/dom/media/mediasource/MediaSourceDemuxer.h
+++ b/dom/media/mediasource/MediaSourceDemuxer.h
@@ -37,17 +37,17 @@ public:
                                                               uint32_t aTrackNumber) override;
 
   bool IsSeekable() const override;
 
   UniquePtr<EncryptionInfo> GetCrypto() override;
 
   bool ShouldComputeStartTime() const override { return false; }
 
-  void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override;
+  void NotifyDataArrived() override;
 
   /* interface for TrackBuffersManager */
   void AttachSourceBuffer(TrackBuffersManager* aSourceBuffer);
   void DetachSourceBuffer(TrackBuffersManager* aSourceBuffer);
   TaskQueue* GetTaskQueue() { return mTaskQueue; }
 
   // Returns a string describing the state of the MediaSource internal
   // buffered data. Used for debugging purposes.
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -871,20 +871,18 @@ TrackBuffersManager::OnDemuxerResetDone(
 
   SegmentParserLoop();
 }
 
 void
 TrackBuffersManager::AppendDataToCurrentInputBuffer(MediaByteBuffer* aData)
 {
   MOZ_ASSERT(mCurrentInputBuffer);
-  int64_t offset = mCurrentInputBuffer->GetLength();
   mCurrentInputBuffer->AppendData(aData);
-  // A MediaByteBuffer has a maximum size of 2GiB.
-  mInputDemuxer->NotifyDataArrived(uint32_t(aData->Length()), offset);
+  mInputDemuxer->NotifyDataArrived();
 }
 
 void
 TrackBuffersManager::InitializationSegmentReceived()
 {
   MOZ_ASSERT(mParser->HasCompleteInitData());
   mCurrentInputBuffer = new SourceBufferResource(mType);
   // The demuxer isn't initialized yet ; we don't want to notify it
--- a/dom/media/omx/MediaCodecProxy.cpp
+++ b/dom/media/omx/MediaCodecProxy.cpp
@@ -9,17 +9,16 @@
 #include <binder/IPCThreadState.h>
 #include <stagefright/foundation/ABuffer.h>
 #include <stagefright/foundation/ADebug.h>
 #include <stagefright/MetaData.h>
 #include "stagefright/MediaErrors.h"
 
 #include <android/log.h>
 #define MCP_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "MediaCodecProxy", __VA_ARGS__)
-#define TIMEOUT_DEQUEUE_INPUTBUFFER_MS 1000000ll
 
 namespace android {
 
 // General Template: MediaCodec::getOutputGraphicBufferFromIndex(...)
 template <typename T, bool InterfaceSupported>
 struct OutputGraphicBufferStub
 {
   static status_t GetOutputGraphicBuffer(T *aMediaCodec,
@@ -566,31 +565,34 @@ bool MediaCodecProxy::UpdateOutputBuffer
   if (err != OK){
     MCP_LOG("Couldn't update output buffers from MediaCodec");
     return false;
   }
   return true;
 }
 
 status_t MediaCodecProxy::Input(const uint8_t* aData, uint32_t aDataSize,
-                                int64_t aTimestampUsecs, uint64_t aflags)
+                                int64_t aTimestampUsecs, uint64_t aflags,
+                                int64_t aTimeoutUs)
 {
   // Read Lock for mCodec
   {
     RWLock::AutoRLock autolock(mCodecLock);
     if (mCodec == nullptr) {
       MCP_LOG("MediaCodec has not been inited from input!");
       return NO_INIT;
     }
   }
 
   size_t index;
-  status_t err = dequeueInputBuffer(&index, TIMEOUT_DEQUEUE_INPUTBUFFER_MS);
+  status_t err = dequeueInputBuffer(&index, aTimeoutUs);
   if (err != OK) {
-    MCP_LOG("dequeueInputBuffer returned %d", err);
+    if (err != -EAGAIN) {
+      MCP_LOG("dequeueInputBuffer returned %d", err);
+    }
     return err;
   }
 
   if (aData) {
     const sp<ABuffer> &dstBuffer = mInputBuffers.itemAt(index);
 
     CHECK_LE(aDataSize, dstBuffer->capacity());
     dstBuffer->setRange(0, aDataSize);
--- a/dom/media/omx/MediaCodecProxy.h
+++ b/dom/media/omx/MediaCodecProxy.h
@@ -124,17 +124,17 @@ public:
                                            sp<GraphicBuffer> *aGraphicBuffer);
 
   status_t getCapability(uint32_t *aCapability);
 
   // Utility functions
 
   // If aData is null, will notify decoder input EOS
   status_t Input(const uint8_t* aData, uint32_t aDataSize,
-                 int64_t aTimestampUsecs, uint64_t flags);
+                 int64_t aTimestampUsecs, uint64_t flags, int64_t aTimeoutUs = 0);
   status_t Output(MediaBuffer** aBuffer, int64_t aTimeoutUs);
   bool Prepare();
   void ReleaseMediaResources();
   // This updates mOutputBuffer when receiving INFO_OUTPUT_BUFFERS_CHANGED event.
   bool UpdateOutputBuffers();
 
   void ReleaseMediaBuffer(MediaBuffer* abuffer);
 
--- a/dom/media/platforms/gonk/GonkAudioDecoderManager.cpp
+++ b/dom/media/platforms/gonk/GonkAudioDecoderManager.cpp
@@ -16,71 +16,66 @@
 #include "stagefright/MetaData.h"
 #include "stagefright/MediaErrors.h"
 #include <stagefright/foundation/AMessage.h>
 #include <stagefright/foundation/ALooper.h>
 #include "media/openmax/OMX_Audio.h"
 #include "MediaData.h"
 #include "MediaInfo.h"
 
+#define CODECCONFIG_TIMEOUT_US 10000LL
+#define READ_OUTPUT_BUFFER_TIMEOUT_US  0LL
+
 #include <android/log.h>
 #define GADM_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "GonkAudioDecoderManager", __VA_ARGS__)
 
 extern PRLogModuleInfo* GetPDMLog();
 #define LOG(...) MOZ_LOG(GetPDMLog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
-#define READ_OUTPUT_BUFFER_TIMEOUT_US  3000
 
 using namespace android;
 typedef android::MediaCodecProxy MediaCodecProxy;
 
 namespace mozilla {
 
 GonkAudioDecoderManager::GonkAudioDecoderManager(const AudioInfo& aConfig)
-  : mLastDecodedTime(0)
-  , mAudioChannels(aConfig.mChannels)
+  : mAudioChannels(aConfig.mChannels)
   , mAudioRate(aConfig.mRate)
   , mAudioProfile(aConfig.mProfile)
   , mAudioBuffer(nullptr)
-  , mMonitor("GonkAudioDecoderManager")
 {
   MOZ_COUNT_CTOR(GonkAudioDecoderManager);
   MOZ_ASSERT(mAudioChannels);
   mCodecSpecificData = aConfig.mCodecSpecificConfig;
   mMimeType = aConfig.mMimeType;
-
 }
 
 GonkAudioDecoderManager::~GonkAudioDecoderManager()
 {
   MOZ_COUNT_DTOR(GonkAudioDecoderManager);
 }
 
 nsRefPtr<MediaDataDecoder::InitPromise>
-GonkAudioDecoderManager::Init(MediaDataDecoderCallback* aCallback)
+GonkAudioDecoderManager::Init()
 {
-  if (InitMediaCodecProxy(aCallback)) {
+  if (InitMediaCodecProxy()) {
     return InitPromise::CreateAndResolve(TrackType::kAudioTrack, __func__);
   } else {
     return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
   }
 }
 
 bool
-GonkAudioDecoderManager::InitMediaCodecProxy(MediaDataDecoderCallback* aCallback)
+GonkAudioDecoderManager::InitMediaCodecProxy()
 {
   status_t rv = OK;
-  if (mLooper != nullptr) {
+  if (!InitLoopers(MediaData::AUDIO_DATA)) {
     return false;
   }
-  // Create ALooper
-  mLooper = new ALooper;
-  mLooper->setName("GonkAudioDecoderManager");
-  mLooper->start();
 
-  mDecoder = MediaCodecProxy::CreateByType(mLooper, mMimeType.get(), false, nullptr);
+  mDecoder = MediaCodecProxy::CreateByType(mDecodeLooper, mMimeType.get(), false, nullptr);
   if (!mDecoder.get()) {
     return false;
   }
   if (!mDecoder->AskMediaCodecAndWait())
   {
     mDecoder = nullptr;
     return false;
   }
@@ -94,73 +89,28 @@ GonkAudioDecoderManager::InitMediaCodecP
   format->setInt32("aac-profile", mAudioProfile);
   status_t err = mDecoder->configure(format, nullptr, nullptr, 0);
   if (err != OK || !mDecoder->Prepare()) {
     return false;
   }
 
   if (mMimeType.EqualsLiteral("audio/mp4a-latm")) {
     rv = mDecoder->Input(mCodecSpecificData->Elements(), mCodecSpecificData->Length(), 0,
-                         android::MediaCodec::BUFFER_FLAG_CODECCONFIG);
+                         android::MediaCodec::BUFFER_FLAG_CODECCONFIG,
+                         CODECCONFIG_TIMEOUT_US);
   }
 
   if (rv == OK) {
     return true;
   } else {
     GADM_LOG("Failed to input codec specific data!");
     return false;
   }
 }
 
-bool
-GonkAudioDecoderManager::HasQueuedSample()
-{
-    MonitorAutoLock mon(mMonitor);
-    return mQueueSample.Length();
-}
-
-nsresult
-GonkAudioDecoderManager::Input(MediaRawData* aSample)
-{
-  MonitorAutoLock mon(mMonitor);
-  nsRefPtr<MediaRawData> sample;
-
-  if (aSample) {
-    sample = aSample;
-  } else {
-    // It means EOS with empty sample.
-    sample = new MediaRawData();
-  }
-
-  mQueueSample.AppendElement(sample);
-
-  status_t rv;
-  while (mQueueSample.Length()) {
-    nsRefPtr<MediaRawData> data = mQueueSample.ElementAt(0);
-    {
-      MonitorAutoUnlock mon_exit(mMonitor);
-      rv = mDecoder->Input(reinterpret_cast<const uint8_t*>(data->Data()),
-                           data->Size(),
-                           data->mTime,
-                           0);
-    }
-    if (rv == OK) {
-      mQueueSample.RemoveElementAt(0);
-    } else if (rv == -EAGAIN || rv == -ETIMEDOUT) {
-      // In most cases, EAGAIN or ETIMEOUT are safe because OMX can't fill
-      // buffer on time.
-      return NS_OK;
-    } else {
-      return NS_ERROR_UNEXPECTED;
-    }
-  }
-
-  return NS_OK;
-}
-
 nsresult
 GonkAudioDecoderManager::CreateAudioData(int64_t aStreamOffset, AudioData **v) {
   if (!(mAudioBuffer != nullptr && mAudioBuffer->data() != nullptr)) {
     GADM_LOG("Audio Buffer is not valid!");
     return NS_ERROR_UNEXPECTED;
   }
 
   int64_t timeUs;
@@ -170,23 +120,23 @@ GonkAudioDecoderManager::CreateAudioData
 
   if (mAudioBuffer->range_length() == 0) {
     // Some decoders may return spurious empty buffers that we just want to ignore
     // quoted from Android's AwesomePlayer.cpp
     ReleaseAudioBuffer();
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  if (mLastDecodedTime > timeUs) {
+  if (mLastTime > timeUs) {
     ReleaseAudioBuffer();
     GADM_LOG("Output decoded sample time is revert. time=%lld", timeUs);
     MOZ_ASSERT(false);
     return NS_ERROR_NOT_AVAILABLE;
   }
-  mLastDecodedTime = timeUs;
+  mLastTime = timeUs;
 
   const uint8_t *data = static_cast<const uint8_t*>(mAudioBuffer->data());
   size_t dataOffset = mAudioBuffer->range_offset();
   size_t size = mAudioBuffer->range_length();
 
   nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[size/2]);
   memcpy(buffer.get(), data+dataOffset, size);
   uint32_t frames = size / (2 * mAudioChannels);
@@ -203,33 +153,16 @@ GonkAudioDecoderManager::CreateAudioData
                                                 mAudioChannels,
                                                 mAudioRate);
   ReleaseAudioBuffer();
   audioData.forget(v);
   return NS_OK;
 }
 
 nsresult
-GonkAudioDecoderManager::Flush()
-{
-  {
-    MonitorAutoLock mon(mMonitor);
-    mQueueSample.Clear();
-  }
-
-  mLastDecodedTime = 0;
-
-  if (mDecoder->flush() != OK) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return NS_OK;
-}
-
-nsresult
 GonkAudioDecoderManager::Output(int64_t aStreamOffset,
                                 nsRefPtr<MediaData>& aOutData)
 {
   aOutData = nullptr;
   status_t err;
   err = mDecoder->Output(&mAudioBuffer, READ_OUTPUT_BUFFER_TIMEOUT_US);
 
   switch (err) {
--- a/dom/media/platforms/gonk/GonkAudioDecoderManager.h
+++ b/dom/media/platforms/gonk/GonkAudioDecoderManager.h
@@ -8,62 +8,43 @@
 #define GonkAudioDecoderManager_h_
 
 #include "mozilla/RefPtr.h"
 #include "GonkMediaDataDecoder.h"
 
 using namespace android;
 
 namespace android {
-struct MOZ_EXPORT ALooper;
 class MOZ_EXPORT MediaBuffer;
 } // namespace android
 
 namespace mozilla {
 
 class GonkAudioDecoderManager : public GonkDecoderManager {
 typedef android::MediaCodecProxy MediaCodecProxy;
 public:
   GonkAudioDecoderManager(const AudioInfo& aConfig);
 
-  virtual ~GonkAudioDecoderManager() override;
+  virtual ~GonkAudioDecoderManager();
 
-  nsRefPtr<InitPromise> Init(MediaDataDecoderCallback* aCallback) override;
-
-  nsresult Input(MediaRawData* aSample) override;
+  nsRefPtr<InitPromise> Init() override;
 
   nsresult Output(int64_t aStreamOffset,
                           nsRefPtr<MediaData>& aOutput) override;
 
-  nsresult Flush() override;
-
-  bool HasQueuedSample() override;
-
 private:
-  bool InitMediaCodecProxy(MediaDataDecoderCallback* aCallback);
+  bool InitMediaCodecProxy();
 
   nsresult CreateAudioData(int64_t aStreamOffset,
                               AudioData** aOutData);
 
   void ReleaseAudioBuffer();
 
-  int64_t mLastDecodedTime;
-
   uint32_t mAudioChannels;
   uint32_t mAudioRate;
   const uint32_t mAudioProfile;
 
-  MediaDataDecoderCallback*  mReaderCallback;
   android::MediaBuffer* mAudioBuffer;
-  android::sp<ALooper> mLooper;
-
-  // This monitor protects mQueueSample.
-  Monitor mMonitor;
-
-  // An queue with the MP4 samples which are waiting to be sent into OMX.
-  // If an element is an empty MP4Sample, that menas EOS. There should not
-  // any sample be queued after EOS.
-  nsTArray<nsRefPtr<MediaRawData>> mQueueSample;
 };
 
 } // namespace mozilla
 
 #endif // GonkAudioDecoderManager_h_
--- a/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp
+++ b/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp
@@ -3,64 +3,282 @@
 /* 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 "GonkMediaDataDecoder.h"
 #include "VideoUtils.h"
 #include "nsTArray.h"
 #include "MediaCodecProxy.h"
 
+#include <stagefright/foundation/ADebug.h>
+
 #include "mozilla/Logging.h"
 #include <android/log.h>
 #define GMDD_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "GonkMediaDataDecoder", __VA_ARGS__)
+#define INPUT_TIMEOUT_US 0LL // Don't wait for buffer if none is available.
+#define MIN_QUEUED_SAMPLES 2
 
 extern PRLogModuleInfo* GetPDMLog();
 #define LOG(...) MOZ_LOG(GetPDMLog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
 
 using namespace android;
 
 namespace mozilla {
 
+bool
+GonkDecoderManager::InitLoopers(MediaData::Type aType)
+{
+  MOZ_ASSERT(mDecodeLooper.get() == nullptr && mTaskLooper.get() == nullptr);
+  MOZ_ASSERT(aType == MediaData::VIDEO_DATA || aType == MediaData::AUDIO_DATA);
+
+  const char* suffix = (aType == MediaData::VIDEO_DATA ? "video" : "audio");
+  mDecodeLooper = new ALooper;
+  android::AString name("MediaCodecProxy/");
+  name.append(suffix);
+  mDecodeLooper->setName(name.c_str());
+
+  mTaskLooper = new ALooper;
+  name.setTo("GonkDecoderManager/");
+  name.append(suffix);
+  mTaskLooper->setName(name.c_str());
+  mTaskLooper->registerHandler(this);
+
+  return mDecodeLooper->start() == OK && mTaskLooper->start() == OK;
+}
+
+nsresult
+GonkDecoderManager::Input(MediaRawData* aSample)
+{
+  nsRefPtr<MediaRawData> sample;
+
+  if (aSample) {
+    sample = aSample;
+  } else {
+    // It means EOS with empty sample.
+    sample = new MediaRawData();
+  }
+  {
+    MutexAutoLock lock(mMutex);
+    mQueuedSamples.AppendElement(sample);
+  }
+
+  sp<AMessage> input = new AMessage(kNotifyProcessInput, id());
+  if (!aSample) {
+    input->setInt32("input-eos", 1);
+  }
+  input->post();
+  return NS_OK;
+}
+
+int32_t
+GonkDecoderManager::ProcessQueuedSamples()
+{
+  MutexAutoLock lock(mMutex);
+  status_t rv;
+  while (mQueuedSamples.Length()) {
+    nsRefPtr<MediaRawData> data = mQueuedSamples.ElementAt(0);
+    {
+      rv = mDecoder->Input(reinterpret_cast<const uint8_t*>(data->Data()),
+                           data->Size(),
+                           data->mTime,
+                           0,
+                           INPUT_TIMEOUT_US);
+    }
+    if (rv == OK) {
+      mQueuedSamples.RemoveElementAt(0);
+      mWaitOutput.AppendElement(data->mOffset);
+    } else if (rv == -EAGAIN || rv == -ETIMEDOUT) {
+      // In most cases, EAGAIN or ETIMEOUT are safe because OMX can't fill
+      // buffer on time.
+      break;
+    } else {
+      return rv;
+    }
+  }
+  return mQueuedSamples.Length();
+}
+
+nsresult
+GonkDecoderManager::Flush()
+{
+  if (mDecoder == nullptr) {
+    GMDD_LOG("Decoder is not initialized");
+    return NS_ERROR_UNEXPECTED;
+  }
+  {
+    MutexAutoLock lock(mMutex);
+    mQueuedSamples.Clear();
+  }
+
+  mLastTime = 0;
+
+  MonitorAutoLock lock(mFlushMonitor);
+  mIsFlushing = true;
+  sp<AMessage> flush = new AMessage(kNotifyProcessFlush, id());
+  flush->post();
+  while (mIsFlushing) {
+    lock.Wait();
+  }
+  return NS_OK;
+}
+
 nsresult
 GonkDecoderManager::Shutdown()
 {
   if (mDecoder.get()) {
     mDecoder->stop();
     mDecoder->ReleaseMediaResources();
     mDecoder = nullptr;
   }
 
   mInitPromise.RejectIfExists(DecoderFailureReason::CANCELED, __func__);
 
   return NS_OK;
 }
 
+bool
+GonkDecoderManager::HasQueuedSample()
+{
+  MutexAutoLock lock(mMutex);
+  return mQueuedSamples.Length();
+}
+
+void
+GonkDecoderManager::ProcessInput(bool aEndOfStream)
+{
+  status_t rv = ProcessQueuedSamples();
+  if (rv >= 0) {
+    if (!aEndOfStream && rv <= MIN_QUEUED_SAMPLES) {
+      mDecodeCallback->InputExhausted();
+    }
+
+    if (mToDo.get() == nullptr) {
+      mToDo = new AMessage(kNotifyDecoderActivity, id());
+      if (aEndOfStream) {
+        mToDo->setInt32("input-eos", 1);
+      }
+      mDecoder->requestActivityNotification(mToDo);
+    }
+  } else {
+    GMDD_LOG("input processed: error#%d", rv);
+    mDecodeCallback->Error();
+  }
+}
+
+void
+GonkDecoderManager::ProcessFlush()
+{
+  MonitorAutoLock lock(mFlushMonitor);
+  mWaitOutput.Clear();
+  if (mDecoder->flush() != OK) {
+    GMDD_LOG("flush error");
+    mDecodeCallback->Error();
+  }
+  mIsFlushing = false;
+  lock.NotifyAll();
+}
+
+void
+GonkDecoderManager::ProcessToDo(bool aEndOfStream)
+{
+  MOZ_ASSERT(mToDo.get() != nullptr);
+  mToDo.clear();
+
+  if (HasQueuedSample()) {
+    status_t pendingInput = ProcessQueuedSamples();
+    if (pendingInput < 0) {
+      mDecodeCallback->Error();
+      return;
+    }
+    if (!aEndOfStream && pendingInput <= MIN_QUEUED_SAMPLES) {
+      mDecodeCallback->InputExhausted();
+    }
+  }
+
+  nsresult rv = NS_OK;
+  while (mWaitOutput.Length() > 0) {
+    nsRefPtr<MediaData> output;
+    int64_t offset = mWaitOutput.ElementAt(0);
+    rv = Output(offset, output);
+    if (rv == NS_OK) {
+      mWaitOutput.RemoveElementAt(0);
+      mDecodeCallback->Output(output);
+    } else if (rv == NS_ERROR_ABORT) {
+      GMDD_LOG("eos output");
+      mWaitOutput.RemoveElementAt(0);
+      MOZ_ASSERT(mQueuedSamples.IsEmpty());
+      MOZ_ASSERT(mWaitOutput.IsEmpty());
+      // EOS
+      if (output) {
+        mDecodeCallback->Output(output);
+      }
+      mDecodeCallback->DrainComplete();
+      return;
+    } else if (rv == NS_ERROR_NOT_AVAILABLE) {
+      break;
+    } else {
+      mDecodeCallback->Error();
+      return;
+    }
+  }
+
+  if (HasQueuedSample() || mWaitOutput.Length() > 0) {
+    mToDo = new AMessage(kNotifyDecoderActivity, id());
+    mDecoder->requestActivityNotification(mToDo);
+  }
+}
+
+void
+GonkDecoderManager::onMessageReceived(const sp<AMessage> &aMessage)
+{
+  switch (aMessage->what()) {
+    case kNotifyProcessInput:
+    {
+      int32_t eos = 0;
+      ProcessInput(aMessage->findInt32("input-eos", &eos) && eos);
+      break;
+    }
+    case kNotifyProcessFlush:
+    {
+      ProcessFlush();
+      break;
+    }
+    case kNotifyDecoderActivity:
+    {
+      int32_t eos = 0;
+      ProcessToDo(aMessage->findInt32("input-eos", &eos) && eos);
+      break;
+    }
+    default:
+      {
+        TRESPASS();
+        break;
+      }
+  }
+}
+
 GonkMediaDataDecoder::GonkMediaDataDecoder(GonkDecoderManager* aManager,
                                            FlushableTaskQueue* aTaskQueue,
                                            MediaDataDecoderCallback* aCallback)
   : mTaskQueue(aTaskQueue)
-  , mCallback(aCallback)
   , mManager(aManager)
-  , mSignaledEOS(false)
-  , mDrainComplete(false)
 {
   MOZ_COUNT_CTOR(GonkMediaDataDecoder);
+  mManager->SetDecodeCallback(aCallback);
 }
 
 GonkMediaDataDecoder::~GonkMediaDataDecoder()
 {
   MOZ_COUNT_DTOR(GonkMediaDataDecoder);
 }
 
 nsRefPtr<MediaDataDecoder::InitPromise>
 GonkMediaDataDecoder::Init()
 {
-  mDrainComplete = false;
-
-  return mManager->Init(mCallback);
+  return mManager->Init();
 }
 
 nsresult
 GonkMediaDataDecoder::Shutdown()
 {
   nsresult rv = mManager->Shutdown();
 
   // Because codec allocated runnable and init promise is at reader TaskQueue,
@@ -68,112 +286,31 @@ GonkMediaDataDecoder::Shutdown()
   mManager = nullptr;
   return rv;
 }
 
 // Inserts data into the decoder's pipeline.
 nsresult
 GonkMediaDataDecoder::Input(MediaRawData* aSample)
 {
-  nsCOMPtr<nsIRunnable> runnable(
-    NS_NewRunnableMethodWithArg<nsRefPtr<MediaRawData>>(
-      this,
-      &GonkMediaDataDecoder::ProcessDecode,
-      nsRefPtr<MediaRawData>(aSample)));
-  mTaskQueue->Dispatch(runnable.forget());
+  mManager->Input(aSample);
   return NS_OK;
 }
 
-void
-GonkMediaDataDecoder::ProcessDecode(MediaRawData* aSample)
-{
-  nsresult rv = mManager->Input(aSample);
-  if (rv != NS_OK) {
-    NS_WARNING("GonkMediaDataDecoder failed to input data");
-    GMDD_LOG("Failed to input data err: %d",int(rv));
-    mCallback->Error();
-    return;
-  }
-  if (aSample) {
-    mLastStreamOffset = aSample->mOffset;
-  }
-  ProcessOutput();
-}
-
-void
-GonkMediaDataDecoder::ProcessOutput()
-{
-  nsRefPtr<MediaData> output;
-  nsresult rv = NS_ERROR_ABORT;
-
-  while (!mDrainComplete) {
-    // There are samples in queue, try to send them into decoder when EOS.
-    if (mSignaledEOS && mManager->HasQueuedSample()) {
-      GMDD_LOG("ProcessOutput: drain all input samples");
-      rv = mManager->Input(nullptr);
-    }
-    rv = mManager->Output(mLastStreamOffset, output);
-    if (rv == NS_OK) {
-      mCallback->Output(output);
-      continue;
-    } else if (rv == NS_ERROR_NOT_AVAILABLE && mSignaledEOS) {
-      // Try to get more frames before getting EOS frame
-      continue;
-    }
-    else {
-      break;
-    }
-  }
-
-  if (rv == NS_ERROR_NOT_AVAILABLE && !mSignaledEOS) {
-    mCallback->InputExhausted();
-    return;
-  }
-  if (rv != NS_OK) {
-    NS_WARNING("GonkMediaDataDecoder failed to output data");
-    GMDD_LOG("Failed to output data");
-    // GonkDecoderManangers report NS_ERROR_ABORT when EOS is reached.
-    if (rv == NS_ERROR_ABORT) {
-      if (output) {
-        mCallback->Output(output);
-      }
-      mCallback->DrainComplete();
-      MOZ_ASSERT_IF(mSignaledEOS, !mManager->HasQueuedSample());
-      mSignaledEOS = false;
-      mDrainComplete = true;
-      return;
-    }
-    GMDD_LOG("Callback error!");
-    mCallback->Error();
-  }
-}
-
 nsresult
 GonkMediaDataDecoder::Flush()
 {
   // Flush the input task queue. This cancels all pending Decode() calls.
   // Note this blocks until the task queue finishes its current job, if
   // it's executing at all. Note the MP4Reader ignores all output while
   // flushing.
   mTaskQueue->Flush();
-  mDrainComplete = false;
   return mManager->Flush();
 }
 
-void
-GonkMediaDataDecoder::ProcessDrain()
-{
-  // Notify decoder input EOS by sending a null data.
-  ProcessDecode(nullptr);
-  mSignaledEOS = true;
-  ProcessOutput();
-}
-
 nsresult
 GonkMediaDataDecoder::Drain()
 {
-  nsCOMPtr<nsIRunnable> runnable =
-    NS_NewRunnableMethod(this, &GonkMediaDataDecoder::ProcessDrain);
-  mTaskQueue->Dispatch(runnable.forget());
+  mManager->Input(nullptr);
   return NS_OK;
 }
 
 } // namespace mozilla
--- a/dom/media/platforms/gonk/GonkMediaDataDecoder.h
+++ b/dom/media/platforms/gonk/GonkMediaDataDecoder.h
@@ -2,66 +2,128 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(GonkMediaDataDecoder_h_)
 #define GonkMediaDataDecoder_h_
 #include "PlatformDecoderModule.h"
+#include <stagefright/foundation/AHandler.h>
 
 namespace android {
+struct ALooper;
 class MediaCodecProxy;
 } // namespace android
 
 namespace mozilla {
 class MediaRawData;
 
 // Manage the data flow from inputting encoded data and outputting decode data.
-class GonkDecoderManager {
+class GonkDecoderManager : public android::AHandler {
 public:
   typedef TrackInfo::TrackType TrackType;
   typedef MediaDataDecoder::InitPromise InitPromise;
   typedef MediaDataDecoder::DecoderFailureReason DecoderFailureReason;
 
   virtual ~GonkDecoderManager() {}
 
-  virtual nsRefPtr<InitPromise> Init(MediaDataDecoderCallback* aCallback) = 0;
-
-  // Add samples into OMX decoder or queue them if decoder is out of input buffer.
-  virtual nsresult Input(MediaRawData* aSample) = 0;
+  virtual nsRefPtr<InitPromise> Init() = 0;
 
-  // Produces decoded output, it blocks until output can be produced or a timeout
-  // is expired or until EOS. Returns NS_OK on success, or NS_ERROR_NOT_AVAILABLE
-  // if there's not enough data to produce more output. If this returns a failure
-  // code other than NS_ERROR_NOT_AVAILABLE, an error will be reported to the
-  // MP4Reader.
-  // The overrided class should follow the same behaviour.
-  virtual nsresult Output(int64_t aStreamOffset,
-                          nsRefPtr<MediaData>& aOutput) = 0;
+  // Asynchronously send sample into mDecoder. If out of input buffer, aSample
+  // will be queued for later re-send.
+  nsresult Input(MediaRawData* aSample);
 
   // Flush the queued sample.
-  virtual nsresult Flush() = 0;
+  nsresult Flush();
 
   // Shutdown decoder and rejects the init promise.
   nsresult Shutdown();
 
   // True if sample is queued.
-  virtual bool HasQueuedSample() = 0;
+  bool HasQueuedSample();
+
+  // Set callback for decoder events, such as requesting more input,
+  // returning output, or reporting error.
+  void SetDecodeCallback(MediaDataDecoderCallback* aCallback)
+  {
+    mDecodeCallback = aCallback;
+  }
 
 protected:
+  GonkDecoderManager()
+    : mMutex("GonkDecoderManager")
+    , mLastTime(0)
+    , mFlushMonitor("GonkDecoderManager::Flush")
+    , mIsFlushing(false)
+    , mDecodeCallback(nullptr)
+  {}
+
+  bool InitLoopers(MediaData::Type aType);
+
+  void onMessageReceived(const android::sp<android::AMessage> &aMessage) override;
+
+  // Produces decoded output. It returns NS_OK on success, or NS_ERROR_NOT_AVAILABLE
+  // when output is not produced yet.
+  // If this returns a failure code other than NS_ERROR_NOT_AVAILABLE, an error
+  // will be reported through mDecodeCallback.
+  virtual nsresult Output(int64_t aStreamOffset,
+                          nsRefPtr<MediaData>& aOutput) = 0;
+
+  // Send queued samples to OMX. It returns how many samples are still in
+  // queue after processing, or negtive error code if failed.
+  int32_t ProcessQueuedSamples();
+
+  void ProcessInput(bool aEndOfStream);
+  void ProcessFlush();
+  void ProcessToDo(bool aEndOfStream);
+
   nsRefPtr<MediaByteBuffer> mCodecSpecificData;
 
   nsAutoCString mMimeType;
 
   // MediaCodedc's wrapper that performs the decoding.
   android::sp<android::MediaCodecProxy> mDecoder;
+  // Looper for mDecoder to run on.
+  android::sp<android::ALooper> mDecodeLooper;
+  // Looper to run decode tasks such as processing input, output, flush, and
+  // recycling output buffers.
+  android::sp<android::ALooper> mTaskLooper;
+  enum {
+    // Decoder will send this to indicate internal state change such as input or
+    // output buffers availability. Used to run pending input & output tasks.
+    kNotifyDecoderActivity = 'nda ',
+    // Signal the decoder to flush.
+    kNotifyProcessFlush = 'npf ',
+    // Used to process queued samples when there is new input.
+    kNotifyProcessInput = 'npi ',
+  };
 
   MozPromiseHolder<InitPromise> mInitPromise;
 
+  Mutex mMutex; // Protects mQueuedSamples.
+  // A queue that stores the samples waiting to be sent to mDecoder.
+  // Empty element means EOS and there shouldn't be any sample be queued after it.
+  // Samples are queued in caller's thread and dequeued in mTaskLooper.
+  nsTArray<nsRefPtr<MediaRawData>> mQueuedSamples;
+
+  int64_t mLastTime;  // The last decoded frame presentation time.
+
+  Monitor mFlushMonitor; // Waits for flushing to complete.
+  bool mIsFlushing;
+
+  // Remembers the notification that is currently waiting for the decoder event
+  // to avoid requesting more than one notification at the time, which is
+  // forbidden by mDecoder.
+  android::sp<android::AMessage> mToDo;
+
+  // Stores the offset of every output that needs to be read from mDecoder.
+  nsTArray<int64_t> mWaitOutput;
+
+  MediaDataDecoderCallback* mDecodeCallback; // Reports decoder output or error.
 };
 
 // Samples are decoded using the GonkDecoder (MediaCodec)
 // created by the GonkDecoderManager. This class implements
 // the higher-level logic that drives mapping the Gonk to the async
 // MediaDataDecoder interface. The specifics of decoding the exact stream
 // type are handled by GonkDecoderManager and the GonkDecoder it creates.
 class GonkMediaDataDecoder : public MediaDataDecoder {
@@ -78,40 +140,16 @@ public:
 
   nsresult Flush() override;
 
   nsresult Drain() override;
 
   nsresult Shutdown() override;
 
 private:
-
-  // Called on the task queue. Inserts the sample into the decoder, and
-  // extracts output if available, if aSample is null, it means there is
-  // no data from source, it will notify the decoder EOS and flush all the
-  // decoded frames.
-  void ProcessDecode(MediaRawData* aSample);
-
-  // Called on the task queue. Extracts output if available, and delivers
-  // it to the reader. Called after ProcessDecode() and ProcessDrain().
-  void ProcessOutput();
+  RefPtr<FlushableTaskQueue> mTaskQueue;
 
-  // Called on the task queue. Orders the Gonk to drain, and then extracts
-  // all available output.
-  void ProcessDrain();
-
-  RefPtr<FlushableTaskQueue> mTaskQueue;
-  MediaDataDecoderCallback* mCallback;
-
-  nsAutoPtr<GonkDecoderManager> mManager;
-
-  // The last offset into the media resource that was passed into Input().
-  // This is used to approximate the decoder's position in the media resource.
-  int64_t mLastStreamOffset;
-  // Set it ture when there is no input data
-  bool mSignaledEOS;
-  // Set if there is no more output data from decoder
-  bool mDrainComplete;
+  android::sp<GonkDecoderManager> mManager;
 };
 
 } // namespace mozilla
 
 #endif // GonkMediaDataDecoder_h_
--- a/dom/media/platforms/gonk/GonkVideoDecoderManager.cpp
+++ b/dom/media/platforms/gonk/GonkVideoDecoderManager.cpp
@@ -12,76 +12,70 @@
 #include "ImageContainer.h"
 #include "VideoUtils.h"
 #include "nsThreadUtils.h"
 #include "Layers.h"
 #include "mozilla/Logging.h"
 #include "stagefright/MediaBuffer.h"
 #include "stagefright/MetaData.h"
 #include "stagefright/MediaErrors.h"
-#include <stagefright/foundation/ADebug.h>
-#include <stagefright/foundation/AMessage.h>
 #include <stagefright/foundation/AString.h>
-#include <stagefright/foundation/ALooper.h>
 #include "GonkNativeWindow.h"
 #include "GonkNativeWindowClient.h"
 #include "mozilla/layers/GrallocTextureClient.h"
 #include "mozilla/layers/TextureClient.h"
 #include <cutils/properties.h>
 
-#define READ_OUTPUT_BUFFER_TIMEOUT_US  3000
+#define CODECCONFIG_TIMEOUT_US 10000LL
+#define READ_OUTPUT_BUFFER_TIMEOUT_US  0LL
 
 #include <android/log.h>
 #define GVDM_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "GonkVideoDecoderManager", __VA_ARGS__)
 
 extern PRLogModuleInfo* GetPDMLog();
 #define LOG(...) MOZ_LOG(GetPDMLog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
 using namespace mozilla::layers;
 using namespace android;
 typedef android::MediaCodecProxy MediaCodecProxy;
 
 namespace mozilla {
 
 GonkVideoDecoderManager::GonkVideoDecoderManager(
   mozilla::layers::ImageContainer* aImageContainer,
   const VideoInfo& aConfig)
   : mImageContainer(aImageContainer)
-  , mReaderCallback(nullptr)
-  , mLastDecodedTime(0)
   , mColorConverterBufferSize(0)
   , mNativeWindow(nullptr)
   , mPendingReleaseItemsLock("GonkVideoDecoderManager::mPendingReleaseItemsLock")
-  , mMonitor("GonkVideoDecoderManager")
 {
   MOZ_COUNT_CTOR(GonkVideoDecoderManager);
   mMimeType = aConfig.mMimeType;
   mVideoWidth  = aConfig.mDisplay.width;
   mVideoHeight = aConfig.mDisplay.height;
   mDisplayWidth = aConfig.mDisplay.width;
   mDisplayHeight = aConfig.mDisplay.height;
   mInfo.mVideo = aConfig;
 
   mCodecSpecificData = aConfig.mCodecSpecificConfig;
   nsIntRect pictureRect(0, 0, mVideoWidth, mVideoHeight);
   nsIntSize frameSize(mVideoWidth, mVideoHeight);
   mPicture = pictureRect;
   mInitialFrame = frameSize;
-  mHandler = new MessageHandler(this);
   mVideoListener = new VideoResourceListener(this);
 
 }
 
 GonkVideoDecoderManager::~GonkVideoDecoderManager()
 {
   mVideoListener->NotifyManagerRelease();
   MOZ_COUNT_DTOR(GonkVideoDecoderManager);
 }
 
 nsRefPtr<MediaDataDecoder::InitPromise>
-GonkVideoDecoderManager::Init(MediaDataDecoderCallback* aCallback)
+GonkVideoDecoderManager::Init()
 {
   nsIntSize displaySize(mDisplayWidth, mDisplayHeight);
   nsIntRect pictureRect(0, 0, mVideoWidth, mVideoHeight);
 
   uint32_t maxWidth, maxHeight;
   char propValue[PROPERTY_VALUE_MAX];
   property_get("ro.moz.omx.hw.max_width", propValue, "-1");
   maxWidth = -1 == atoi(propValue) ? MAX_VIDEO_WIDTH : atoi(propValue);
@@ -96,94 +90,41 @@ GonkVideoDecoderManager::Init(MediaDataD
   // Validate the container-reported frame and pictureRect sizes. This ensures
   // that our video frame creation code doesn't overflow.
   nsIntSize frameSize(mVideoWidth, mVideoHeight);
   if (!IsValidVideoRegion(frameSize, pictureRect, displaySize)) {
     GVDM_LOG("It is not a valid region");
     return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
   }
 
-  mReaderCallback = aCallback;
-
   mReaderTaskQueue = AbstractThread::GetCurrent()->AsTaskQueue();
   MOZ_ASSERT(mReaderTaskQueue);
 
-  if (mLooper.get() != nullptr) {
+  if (mDecodeLooper.get() != nullptr) {
     return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
   }
-  // Create ALooper
-  mLooper = new ALooper;
-  mManagerLooper = new ALooper;
-  mManagerLooper->setName("GonkVideoDecoderManager");
-  // Register AMessage handler to ALooper.
-  mManagerLooper->registerHandler(mHandler);
-  // Start ALooper thread.
-  if (mLooper->start() != OK || mManagerLooper->start() != OK ) {
+
+  if (!InitLoopers(MediaData::VIDEO_DATA)) {
     return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
   }
+
   nsRefPtr<InitPromise> p = mInitPromise.Ensure(__func__);
-  mDecoder = MediaCodecProxy::CreateByType(mLooper, mMimeType.get(), false, mVideoListener);
+  mDecoder = MediaCodecProxy::CreateByType(mDecodeLooper, mMimeType.get(), false, mVideoListener);
   mDecoder->AsyncAskMediaCodec();
 
   uint32_t capability = MediaCodecProxy::kEmptyCapability;
   if (mDecoder->getCapability(&capability) == OK && (capability &
       MediaCodecProxy::kCanExposeGraphicBuffer)) {
     mNativeWindow = new GonkNativeWindow();
   }
 
   return p;
 }
 
 nsresult
-GonkVideoDecoderManager::Input(MediaRawData* aSample)
-{
-  MonitorAutoLock mon(mMonitor);
-  nsRefPtr<MediaRawData> sample;
-
-  if (!aSample) {
-    // It means EOS with empty sample.
-    sample = new MediaRawData();
-  } else {
-    sample = aSample;
-  }
-
-  mQueueSample.AppendElement(sample);
-
-  status_t rv;
-  while (mQueueSample.Length()) {
-    nsRefPtr<MediaRawData> data = mQueueSample.ElementAt(0);
-    {
-      MonitorAutoUnlock mon_unlock(mMonitor);
-      rv = mDecoder->Input(reinterpret_cast<const uint8_t*>(data->Data()),
-                           data->Size(),
-                           data->mTime,
-                           0);
-    }
-    if (rv == OK) {
-      mQueueSample.RemoveElementAt(0);
-    } else if (rv == -EAGAIN || rv == -ETIMEDOUT) {
-      // In most cases, EAGAIN or ETIMEOUT are safe because OMX can't fill
-      // buffer on time.
-      return NS_OK;
-    } else {
-      return NS_ERROR_UNEXPECTED;
-    }
-  }
-
-  return NS_OK;
-}
-
-bool
-GonkVideoDecoderManager::HasQueuedSample()
-{
-    MonitorAutoLock mon(mMonitor);
-    return mQueueSample.Length();
-}
-
-nsresult
 GonkVideoDecoderManager::CreateVideoData(int64_t aStreamOffset, VideoData **v)
 {
   *v = nullptr;
   nsRefPtr<VideoData> data;
   int64_t timeUs;
   int32_t keyFrame;
 
   if (mVideoBuffer == nullptr) {
@@ -192,22 +133,22 @@ GonkVideoDecoderManager::CreateVideoData
   }
 
   if (!mVideoBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) {
     ReleaseVideoBuffer();
     GVDM_LOG("Decoder did not return frame time");
     return NS_ERROR_UNEXPECTED;
   }
 
-  if (mLastDecodedTime > timeUs) {
+  if (mLastTime > timeUs) {
     ReleaseVideoBuffer();
     GVDM_LOG("Output decoded sample time is revert. time=%lld", timeUs);
     return NS_ERROR_NOT_AVAILABLE;
   }
-  mLastDecodedTime = timeUs;
+  mLastTime = timeUs;
 
   if (mVideoBuffer->range_length() == 0) {
     // Some decoders may return spurious empty buffers that we just want to ignore
     // quoted from Android's AwesomePlayer.cpp
     ReleaseVideoBuffer();
     return NS_ERROR_NOT_AVAILABLE;
   }
 
@@ -362,37 +303,16 @@ GonkVideoDecoderManager::SetVideoFormat(
       return false;
     }
     return true;
   }
   GVDM_LOG("Fail to get output format");
   return false;
 }
 
-nsresult
-GonkVideoDecoderManager::Flush()
-{
-  if (mDecoder == nullptr) {
-    GVDM_LOG("Decoder is not inited");
-    return NS_ERROR_UNEXPECTED;
-  }
-  {
-    MonitorAutoLock mon(mMonitor);
-    mQueueSample.Clear();
-  }
-
- mLastDecodedTime = 0;
-
-  if (mDecoder->flush() != OK) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return NS_OK;
-}
-
 // Blocks until decoded sample is produced by the deoder.
 nsresult
 GonkVideoDecoderManager::Output(int64_t aStreamOffset,
                                 nsRefPtr<MediaData>& aOutData)
 {
   aOutData = nullptr;
   status_t err;
   if (mDecoder == nullptr) {
@@ -492,17 +412,18 @@ GonkVideoDecoderManager::codecReserved()
     surface = new Surface(mNativeWindow->getBufferQueue());
   }
   mDecoder->configure(format, surface, nullptr, 0);
   mDecoder->Prepare();
 
   if (mMimeType.EqualsLiteral("video/mp4v-es")) {
     rv = mDecoder->Input(mCodecSpecificData->Elements(),
                          mCodecSpecificData->Length(), 0,
-                         android::MediaCodec::BUFFER_FLAG_CODECCONFIG);
+                         android::MediaCodec::BUFFER_FLAG_CODECCONFIG,
+                         CODECCONFIG_TIMEOUT_US);
   }
 
   if (rv != OK) {
     GVDM_LOG("Failed to configure codec!!!!");
     mInitPromise.Reject(DecoderFailureReason::INIT_ERROR, __func__);
     return;
   }
 
@@ -511,48 +432,32 @@ GonkVideoDecoderManager::codecReserved()
 
 void
 GonkVideoDecoderManager::codecCanceled()
 {
   GVDM_LOG("codecCanceled");
   mInitPromise.RejectIfExists(DecoderFailureReason::CANCELED, __func__);
 }
 
-// Called on GonkVideoDecoderManager::mManagerLooper thread.
+// Called on GonkDecoderManager::mTaskLooper thread.
 void
 GonkVideoDecoderManager::onMessageReceived(const sp<AMessage> &aMessage)
 {
   switch (aMessage->what()) {
     case kNotifyPostReleaseBuffer:
     {
       ReleaseAllPendingVideoBuffers();
       break;
     }
 
     default:
-      TRESPASS();
+    {
+      GonkDecoderManager::onMessageReceived(aMessage);
       break;
-  }
-}
-
-GonkVideoDecoderManager::MessageHandler::MessageHandler(GonkVideoDecoderManager *aManager)
-  : mManager(aManager)
-{
-}
-
-GonkVideoDecoderManager::MessageHandler::~MessageHandler()
-{
-  mManager = nullptr;
-}
-
-void
-GonkVideoDecoderManager::MessageHandler::onMessageReceived(const android::sp<android::AMessage> &aMessage)
-{
-  if (mManager != nullptr) {
-    mManager->onMessageReceived(aMessage);
+    }
   }
 }
 
 GonkVideoDecoderManager::VideoResourceListener::VideoResourceListener(GonkVideoDecoderManager *aManager)
   : mManager(aManager)
 {
 }
 
@@ -647,17 +552,17 @@ void GonkVideoDecoderManager::PostReleas
 {
   {
     MutexAutoLock autoLock(mPendingReleaseItemsLock);
     if (aBuffer) {
       mPendingReleaseItems.AppendElement(ReleaseItem(aBuffer, aReleaseFence));
     }
   }
   sp<AMessage> notify =
-            new AMessage(kNotifyPostReleaseBuffer, mHandler->id());
+            new AMessage(kNotifyPostReleaseBuffer, id());
   notify->post();
 
 }
 
 void GonkVideoDecoderManager::ReleaseAllPendingVideoBuffers()
 {
   nsTArray<ReleaseItem> releasingItems;
   {
--- a/dom/media/platforms/gonk/GonkVideoDecoderManager.h
+++ b/dom/media/platforms/gonk/GonkVideoDecoderManager.h
@@ -2,32 +2,29 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(GonkVideoDecoderManager_h_)
 #define GonkVideoDecoderManager_h_
 
-#include <set>
 #include "nsRect.h"
 #include "GonkMediaDataDecoder.h"
 #include "mozilla/RefPtr.h"
 #include "I420ColorConverterHelper.h"
 #include "MediaCodecProxy.h"
-#include <stagefright/foundation/AHandler.h>
 #include "GonkNativeWindow.h"
 #include "GonkNativeWindowClient.h"
 #include "mozilla/layers/FenceUtils.h"
 #include <ui/Fence.h>
 
 using namespace android;
 
 namespace android {
-struct ALooper;
 class MediaBuffer;
 struct MOZ_EXPORT AString;
 class GonkNativeWindow;
 } // namespace android
 
 namespace mozilla {
 
 namespace layers {
@@ -37,61 +34,40 @@ class TextureClient;
 class GonkVideoDecoderManager : public GonkDecoderManager {
 typedef android::MediaCodecProxy MediaCodecProxy;
 typedef mozilla::layers::TextureClient TextureClient;
 
 public:
   GonkVideoDecoderManager(mozilla::layers::ImageContainer* aImageContainer,
                           const VideoInfo& aConfig);
 
-  virtual ~GonkVideoDecoderManager() override;
+  virtual ~GonkVideoDecoderManager();
 
-  nsRefPtr<InitPromise> Init(MediaDataDecoderCallback* aCallback) override;
-
-  nsresult Input(MediaRawData* aSample) override;
+  nsRefPtr<InitPromise> Init() override;
 
   nsresult Output(int64_t aStreamOffset,
                           nsRefPtr<MediaData>& aOutput) override;
 
-  nsresult Flush() override;
-
-  bool HasQueuedSample() override;
-
   static void RecycleCallback(TextureClient* aClient, void* aClosure);
 
 private:
   struct FrameInfo
   {
     int32_t mWidth = 0;
     int32_t mHeight = 0;
     int32_t mStride = 0;
     int32_t mSliceHeight = 0;
     int32_t mColorFormat = 0;
     int32_t mCropLeft = 0;
     int32_t mCropTop = 0;
     int32_t mCropRight = 0;
     int32_t mCropBottom = 0;
   };
-  class MessageHandler : public android::AHandler
-  {
-  public:
-    MessageHandler(GonkVideoDecoderManager *aManager);
-    ~MessageHandler();
 
-    void onMessageReceived(const android::sp<android::AMessage> &aMessage) override;
-
-  private:
-    // Forbidden
-    MessageHandler() = delete;
-    MessageHandler(const MessageHandler &rhs) = delete;
-    const MessageHandler &operator=(const MessageHandler &rhs) = delete;
-
-    GonkVideoDecoderManager *mManager;
-  };
-  friend class MessageHandler;
+  void onMessageReceived(const android::sp<android::AMessage> &aMessage) override;
 
   class VideoResourceListener : public android::MediaCodecProxy::CodecResourceListener
   {
   public:
     VideoResourceListener(GonkVideoDecoderManager *aManager);
     ~VideoResourceListener();
 
     void codecReserved() override;
@@ -114,43 +90,36 @@ private:
 
   nsresult CreateVideoData(int64_t aStreamOffset, VideoData** aOutData);
   void ReleaseVideoBuffer();
   uint8_t* GetColorConverterBuffer(int32_t aWidth, int32_t aHeight);
 
   // For codec resource management
   void codecReserved();
   void codecCanceled();
-  void onMessageReceived(const sp<AMessage> &aMessage);
 
   void ReleaseAllPendingVideoBuffers();
   void PostReleaseVideoBuffer(android::MediaBuffer *aBuffer,
                               layers::FenceHandle mReleaseFence);
 
   uint32_t mVideoWidth;
   uint32_t mVideoHeight;
   uint32_t mDisplayWidth;
   uint32_t mDisplayHeight;
   nsIntRect mPicture;
   nsIntSize mInitialFrame;
 
   nsRefPtr<layers::ImageContainer> mImageContainer;
 
   android::MediaBuffer* mVideoBuffer;
 
-  MediaDataDecoderCallback*  mReaderCallback;
   MediaInfo mInfo;
   android::sp<VideoResourceListener> mVideoListener;
-  android::sp<MessageHandler> mHandler;
-  android::sp<ALooper> mLooper;
-  android::sp<ALooper> mManagerLooper;
   FrameInfo mFrameInfo;
 
-  int64_t mLastDecodedTime;  // The last decoded frame presentation time.
-
   // color converter
   android::I420ColorConverterHelper mColorConverter;
   nsAutoArrayPtr<uint8_t> mColorConverterBuffer;
   size_t mColorConverterBufferSize;
 
   android::sp<android::GonkNativeWindow> mNativeWindow;
   enum {
     kNotifyPostReleaseBuffer = 'nprb',
@@ -163,24 +132,16 @@ private:
     android::MediaBuffer* mBuffer;
     layers::FenceHandle mReleaseFence;
   };
   nsTArray<ReleaseItem> mPendingReleaseItems;
 
   // The lock protects mPendingReleaseItems.
   Mutex mPendingReleaseItemsLock;
 
-  // This monitor protects mQueueSample.
-  Monitor mMonitor;
-
-  // This TaskQueue should be the same one in mReaderCallback->OnReaderTaskQueue().
+  // This TaskQueue should be the same one in mDecodeCallback->OnReaderTaskQueue().
   // It is for codec resource mangement, decoding task should not dispatch to it.
   nsRefPtr<TaskQueue> mReaderTaskQueue;
-
-  // An queue with the MP4 samples which are waiting to be sent into OMX.
-  // If an element is an empty MP4Sample, that menas EOS. There should not
-  // any sample be queued after EOS.
-  nsTArray<nsRefPtr<MediaRawData>> mQueueSample;
 };
 
 } // namespace mozilla
 
 #endif // GonkVideoDecoderManager_h_
--- a/dom/media/webaudio/WaveShaperNode.cpp
+++ b/dom/media/webaudio/WaveShaperNode.cpp
@@ -225,36 +225,44 @@ public:
       // Optimize the case where we don't have a curve buffer,
       // or the input is null.
       *aOutput = aInput;
       return;
     }
 
     aOutput->AllocateChannels(channelCount);
     for (uint32_t i = 0; i < channelCount; ++i) {
-      float* scaledSample = (float *)(aInput.mChannelData[i]);
-      AudioBlockInPlaceScale(scaledSample, aInput.mVolume);
-      const float* inputBuffer = static_cast<const float*>(scaledSample);
+      const float* inputSamples;
+      float scaledInput[WEBAUDIO_BLOCK_SIZE];
+      if (aInput.mVolume != 1.0f) {
+        AudioBlockCopyChannelWithScale(
+            static_cast<const float*>(aInput.mChannelData[i]),
+                                      aInput.mVolume,
+                                      scaledInput);
+        inputSamples = scaledInput;
+      } else {
+        inputSamples = static_cast<const float*>(aInput.mChannelData[i]);
+      }
       float* outputBuffer = aOutput->ChannelFloatsForWrite(i);
       float* sampleBuffer;
 
       switch (mType) {
       case OverSampleType::None:
         mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::None);
-        ProcessCurve<1>(inputBuffer, outputBuffer);
+        ProcessCurve<1>(inputSamples, outputBuffer);
         break;
       case OverSampleType::_2x:
         mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::_2x);
-        sampleBuffer = mResampler.UpSample(i, inputBuffer, 2);
+        sampleBuffer = mResampler.UpSample(i, inputSamples, 2);
         ProcessCurve<2>(sampleBuffer, sampleBuffer);
         mResampler.DownSample(i, outputBuffer, 2);
         break;
       case OverSampleType::_4x:
         mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::_4x);
-        sampleBuffer = mResampler.UpSample(i, inputBuffer, 4);
+        sampleBuffer = mResampler.UpSample(i, inputSamples, 4);
         ProcessCurve<4>(sampleBuffer, sampleBuffer);
         mResampler.DownSample(i, outputBuffer, 4);
         break;
       default:
         NS_NOTREACHED("We should never reach here");
       }
     }
   }
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -167,11 +167,12 @@ skip-if = (toolkit == 'gonk' && !debug) 
 [test_scriptProcessorNodePassThrough.html]
 [test_scriptProcessorNode_playbackTime1.html]
 [test_scriptProcessorNodeZeroInputOutput.html]
 [test_scriptProcessorNodeNotConnected.html]
 [test_singleSourceDest.html]
 [test_stereoPanningWithGain.html]
 [test_waveDecoder.html]
 [test_waveShaper.html]
+[test_waveShaperGain.html]
 [test_waveShaperNoCurve.html]
 [test_waveShaperPassThrough.html]
 [test_waveShaperInvalidLengthCurve.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/test_waveShaperGain.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+  <title>Test that WaveShaperNode doesn't corrupt its inputs when the gain is !=
+    1.0 (bug 1203616)</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+var samplerate = 44100;
+var context = new OfflineAudioContext(1, 44100, samplerate);
+
+var dc = context.createBufferSource();
+
+var buffer = context.createBuffer(1, 1, samplerate);
+buffer.getChannelData(0)[0] = 1.0;
+dc.buffer = buffer;
+
+var gain = context.createGain();
+var ws2 = context.createWaveShaper();
+var ws = [];
+
+// No-op waveshaper curves.
+for (var i = 0; i < 2; i++) {
+  ws[i] = context.createWaveShaper();
+  var curve = new Float32Array(2);
+  curve[0] = -1.0;
+  curve[1] = 1.0;
+  ws[i].curve = curve;
+  ws[i].connect(context.destination);
+  gain.connect(ws[i]);
+}
+
+dc.connect(gain);
+dc.start();
+
+gain.gain.value = 0.5;
+
+context.startRendering().then(buffer => {
+  document.querySelector("pre").innerHTML = buffer.getChannelData(0)[0];
+  ok(buffer.getChannelData(0)[0] == 1.0, "Volume was handled properly");
+  SimpleTest.finish();
+});
+</script>
+<pre>
+</pre>
+</body>
+
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -457,19 +457,19 @@ WebMDemuxer::EnsureUpToDateIndex()
   if (!mIsMediaSource) {
     return;
   }
   mLastWebMBlockOffset = mBufferedState->GetLastBlockOffset();
   MOZ_ASSERT(mLastWebMBlockOffset <= mResource.GetLength());
 }
 
 void
-WebMDemuxer::NotifyDataArrived(uint32_t aLength, int64_t aOffset)
+WebMDemuxer::NotifyDataArrived()
 {
-  WEBM_DEBUG("length: %ld offset: %ld", aLength, aOffset);
+  WEBM_DEBUG("");
   mNeedReIndex = true;
 }
 
 void
 WebMDemuxer::NotifyDataRemoved()
 {
   mBufferedState->Reset();
   if (mInitData) {
--- a/dom/media/webm/WebMDemuxer.h
+++ b/dom/media/webm/WebMDemuxer.h
@@ -124,17 +124,17 @@ public:
 
 private:
   friend class WebMTrackDemuxer;
 
   ~WebMDemuxer();
   void Cleanup();
   void InitBufferedState();
   nsresult ReadMetadata();
-  void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override;
+  void NotifyDataArrived() override;
   void NotifyDataRemoved() override;
   void EnsureUpToDateIndex();
   media::TimeIntervals GetBuffered();
   virtual nsresult SeekInternal(const media::TimeUnit& aTarget);
 
   // Read a packet from the nestegg file. Returns nullptr if all packets for
   // the particular track have been read. Pass TrackInfo::kVideoTrack or
   // TrackInfo::kVideoTrack to indicate the type of the packet we want to read.
--- a/dom/presentation/PresentationReceiver.cpp
+++ b/dom/presentation/PresentationReceiver.cpp
@@ -163,17 +163,17 @@ PresentationReceiver::NotifySessionConne
                                            const nsAString& aSessionId)
 {
   if (NS_WARN_IF(aWindowId != GetOwner()->WindowID())) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsRefPtr<PresentationSession> session =
     PresentationSession::Create(GetOwner(), aSessionId,
-                                PresentationSessionState::Disconnected);
+                                PresentationSessionState::Closed);
   if (NS_WARN_IF(!session)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   mSessions.AppendElement(session);
 
   // Resolve pending |GetSession| promises if any.
   if (!mPendingGetSessionPromises.IsEmpty()) {
     for(uint32_t i = 0; i < mPendingGetSessionPromises.Length(); i++) {
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -424,27 +424,41 @@ PresentationService::SendSessionMessage(
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return info->Send(aStream);
 }
 
 NS_IMETHODIMP
-PresentationService::Terminate(const nsAString& aSessionId)
+PresentationService::CloseSession(const nsAString& aSessionId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!aSessionId.IsEmpty());
 
   nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  return info->Close(NS_OK);
+  return info->Close(NS_OK, nsIPresentationSessionListener::STATE_CLOSED);
+}
+
+NS_IMETHODIMP
+PresentationService::TerminateSession(const nsAString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aSessionId.IsEmpty());
+
+  nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return info->Close(NS_OK, nsIPresentationSessionListener::STATE_TERMINATED);
 }
 
 NS_IMETHODIMP
 PresentationService::RegisterAvailabilityListener(nsIPresentationAvailabilityListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (NS_WARN_IF(mAvailabilityListeners.Contains(aListener))) {
@@ -490,17 +504,17 @@ PresentationService::RegisterSessionList
 
 NS_IMETHODIMP
 PresentationService::UnregisterSessionListener(const nsAString& aSessionId)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
   if (info) {
-    NS_WARN_IF(NS_FAILED(info->Close(NS_OK)));
+    NS_WARN_IF(NS_FAILED(info->Close(NS_OK, nsIPresentationSessionListener::STATE_TERMINATED)));
     UntrackSessionInfo(aSessionId);
     return info->SetListener(nullptr);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationService::RegisterRespondingListener(uint64_t aWindowId,
--- a/dom/presentation/PresentationSession.cpp
+++ b/dom/presentation/PresentationSession.cpp
@@ -137,58 +137,71 @@ PresentationSession::Send(const nsAStrin
   if(NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if(NS_WARN_IF(!service)) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
     return;
   }
 
   rv = service->SendSessionMessage(mId, stream);
   if(NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
   }
 }
 
 void
-PresentationSession::Close()
+PresentationSession::Close(ErrorResult& aRv)
 {
-  // Closing does nothing if the session is already terminated.
-  if (NS_WARN_IF(mState == PresentationSessionState::Terminated)) {
+  // It only works when the state is CONNECTED.
+  if (NS_WARN_IF(mState != PresentationSessionState::Connected)) {
+    return;
+  }
+
+  // TODO Bug 1210340 - Support close semantics.
+  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+void
+PresentationSession::Terminate(ErrorResult& aRv)
+{
+  // It only works when the state is CONNECTED.
+  if (NS_WARN_IF(mState != PresentationSessionState::Connected)) {
     return;
   }
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if(NS_WARN_IF(!service)) {
+    aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
     return;
   }
 
-  NS_WARN_IF(NS_FAILED(service->Terminate(mId)));
+  NS_WARN_IF(NS_FAILED(service->TerminateSession(mId)));
 }
 
 NS_IMETHODIMP
 PresentationSession::NotifyStateChange(const nsAString& aSessionId,
                                        uint16_t aState)
 {
   if (!aSessionId.Equals(mId)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   PresentationSessionState state;
   switch (aState) {
     case nsIPresentationSessionListener::STATE_CONNECTED:
       state = PresentationSessionState::Connected;
       break;
-    case nsIPresentationSessionListener::STATE_DISCONNECTED:
-      state = PresentationSessionState::Disconnected;
+    case nsIPresentationSessionListener::STATE_CLOSED:
+      state = PresentationSessionState::Closed;
       break;
     case nsIPresentationSessionListener::STATE_TERMINATED:
       state = PresentationSessionState::Terminated;
       break;
     default:
       NS_WARNING("Unknown presentation session state.");
       return NS_ERROR_INVALID_ARG;
   }
--- a/dom/presentation/PresentationSession.h
+++ b/dom/presentation/PresentationSession.h
@@ -18,34 +18,36 @@ class PresentationSession final : public
                                 , public nsIPresentationSessionListener
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PresentationSession,
                                            DOMEventTargetHelper)
   NS_DECL_NSIPRESENTATIONSESSIONLISTENER
 
-  static already_AddRefed<PresentationSession>
-    Create(nsPIDOMWindow* aWindow,
-           const nsAString& aId,
-           PresentationSessionState aState);
+  static already_AddRefed<PresentationSession> Create(nsPIDOMWindow* aWindow,
+                                                      const nsAString& aId,
+                                                      PresentationSessionState aState);
 
   virtual void DisconnectFromOwner() override;
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL (public APIs)
   void GetId(nsAString& aId) const;
 
   PresentationSessionState State() const;
 
-  void Send(const nsAString& aData, ErrorResult& aRv);
+  void Send(const nsAString& aData,
+            ErrorResult& aRv);
 
-  void Close();
+  void Close(ErrorResult& aRv);
+
+  void Terminate(ErrorResult& aRv);
 
   IMPL_EVENT_HANDLER(statechange);
   IMPL_EVENT_HANDLER(message);
 
 private:
   PresentationSession(nsPIDOMWindow* aWindow,
                       const nsAString& aId,
                       PresentationSessionState aState);
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -169,20 +169,17 @@ PresentationSessionInfo::SetListener(nsI
       nsresult rv = mTransport->EnableDataNotification();
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
     // The transport might become ready, or might become un-ready again, before
     // the listener has registered. So notify the listener of the state change.
-    uint16_t state = IsSessionReady() ?
-                     nsIPresentationSessionListener::STATE_CONNECTED :
-                     nsIPresentationSessionListener::STATE_DISCONNECTED;
-    return mListener->NotifyStateChange(mSessionId, state);
+    return mListener->NotifyStateChange(mSessionId, mState);
   }
 
   return NS_OK;
 }
 
 nsresult
 PresentationSessionInfo::Send(nsIInputStream* aData)
 {
@@ -193,46 +190,33 @@ PresentationSessionInfo::Send(nsIInputSt
   if (NS_WARN_IF(!mTransport)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return mTransport->Send(aData);
 }
 
 nsresult
-PresentationSessionInfo::Close(nsresult aReason)
+PresentationSessionInfo::Close(nsresult aReason,
+                               uint32_t aState)
 {
-  // The session is disconnected and it's a normal close. Simply change the
-  // state to TERMINATED.
-  if (!IsSessionReady() && NS_SUCCEEDED(aReason)) {
-    if (mListener) {
-      // Notify the listener and the service will untrack the session info after
-      // the listener calls |UnregisterSessionListener|.
-      nsresult rv = mListener->NotifyStateChange(mSessionId,
-                                                 nsIPresentationSessionListener::STATE_TERMINATED);
-      NS_WARN_IF(NS_FAILED(rv));
-    } else {
-      // Directly untrack the session info from the service.
-      NS_WARN_IF(NS_FAILED(UntrackFromService()));
-    }
+  if (NS_WARN_IF(!IsSessionReady())) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
+  SetState(aState);
+
   Shutdown(aReason);
   return NS_OK;
 }
 
 nsresult
 PresentationSessionInfo::ReplySuccess()
 {
-  if (mListener) {
-    // Notify session state change.
-    nsresult rv = mListener->NotifyStateChange(mSessionId,
-                                               nsIPresentationSessionListener::STATE_CONNECTED);
-    NS_WARN_IF(NS_FAILED(rv));
-  }
+  SetState(nsIPresentationSessionListener::STATE_CONNECTED);
 
   if (mCallback) {
     NS_WARN_IF(NS_FAILED(mCallback->NotifySuccess()));
     SetCallback(nullptr);
   }
 
   return NS_OK;
 }
@@ -303,27 +287,24 @@ PresentationSessionInfo::NotifyTransport
   if (NS_WARN_IF(!IsSessionReady())) {
     // It happens before the session is ready. Reply the callback.
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   // Unset |mIsTransportReady| here so it won't affect |IsSessionReady()| above.
   mIsTransportReady = false;
 
+  if (mState == nsIPresentationSessionListener::STATE_CONNECTED) {
+    // The transport channel is closed unexpectedly (not caused by a |Close| call).
+    SetState(nsIPresentationSessionListener::STATE_CLOSED);
+  }
+
   Shutdown(aReason);
 
-  uint16_t state = (NS_WARN_IF(NS_FAILED(aReason))) ?
-                   nsIPresentationSessionListener::STATE_DISCONNECTED :
-                   nsIPresentationSessionListener::STATE_TERMINATED;
-  if (mListener) {
-    // It happens after the session is ready. Notify session state change.
-    // If the new state is TERMINATED. the service will untrack the session info
-    // after the listener calls |UnregisterSessionListener|.
-    return mListener->NotifyStateChange(mSessionId, state);
-  } else if (state == nsIPresentationSessionListener::STATE_TERMINATED) {
+  if (mState == nsIPresentationSessionListener::STATE_TERMINATED) {
     // Directly untrack the session info from the service.
     return UntrackFromService();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -504,22 +485,19 @@ PresentationControllingInfo::NotifyClose
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Unset control channel here so it won't try to re-close it in potential
   // subsequent |Shutdown| calls.
   SetControlChannel(nullptr);
 
   if (NS_WARN_IF(NS_FAILED(aReason))) {
-    if (mListener) {
-      // The presentation session instance at receiver side may already exist.
-      // Change the state to TERMINATED since it never succeeds.
-      return mListener->NotifyStateChange(mSessionId,
-                                          nsIPresentationSessionListener::STATE_TERMINATED);
-    }
+    // The presentation session instance may already exist.
+    // Change the state to TERMINATED since it never succeeds.
+    SetState(nsIPresentationSessionListener::STATE_TERMINATED);
 
     // Reply error for an abnormal close.
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   return NS_OK;
 }
 
@@ -561,21 +539,18 @@ PresentationControllingInfo::OnStopListe
 
   Shutdown(aStatus);
 
   if (NS_WARN_IF(!IsSessionReady())) {
     // It happens before the session is ready. Reply the callback.
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
-  // It happens after the session is ready. Notify session state change.
-  if (mListener) {
-    return mListener->NotifyStateChange(mSessionId,
-                                        nsIPresentationSessionListener::STATE_DISCONNECTED);
-  }
+  // It happens after the session is ready. Change the state to CLOSED.
+  SetState(nsIPresentationSessionListener::STATE_CLOSED);
 
   return NS_OK;
 }
 
 /*
  * Implementation of PresentationPresentingInfo
  *
  * During presentation session establishment, the receiver expects the following
@@ -770,22 +745,19 @@ PresentationPresentingInfo::NotifyClosed
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Unset control channel here so it won't try to re-close it in potential
   // subsequent |Shutdown| calls.
   SetControlChannel(nullptr);
 
   if (NS_WARN_IF(NS_FAILED(aReason))) {
-    if (mListener) {
-      // The presentation session instance at receiver side may already exist.
-      // Change the state to TERMINATED since it never succeeds.
-      return mListener->NotifyStateChange(mSessionId,
-                                          nsIPresentationSessionListener::STATE_TERMINATED);
-    }
+    // The presentation session instance may already exist.
+    // Change the state to TERMINATED since it never succeeds.
+    SetState(nsIPresentationSessionListener::STATE_TERMINATED);
 
     // Reply error for an abnormal close.
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   return NS_OK;
 }
 
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -34,16 +34,17 @@ public:
 
   PresentationSessionInfo(const nsAString& aUrl,
                           const nsAString& aSessionId,
                           nsIPresentationServiceCallback* aCallback)
     : mUrl(aUrl)
     , mSessionId(aSessionId)
     , mIsResponderReady(false)
     , mIsTransportReady(false)
+    , mState(nsIPresentationSessionListener::STATE_CLOSED)
     , mCallback(aCallback)
   {
     MOZ_ASSERT(!mUrl.IsEmpty());
     MOZ_ASSERT(!mSessionId.IsEmpty());
   }
 
   virtual nsresult Init(nsIPresentationControlChannel* aControlChannel);
 
@@ -84,17 +85,18 @@ public:
     mControlChannel = aControlChannel;
     if (mControlChannel) {
       mControlChannel->SetListener(this);
     }
   }
 
   nsresult Send(nsIInputStream* aData);
 
-  nsresult Close(nsresult aReason);
+  nsresult Close(nsresult aReason,
+                 uint32_t aState);
 
   nsresult ReplyError(nsresult aReason);
 
   virtual bool IsAccessible(base::ProcessId aProcessId);
 
 protected:
   virtual ~PresentationSessionInfo()
   {
@@ -107,20 +109,36 @@ protected:
 
   bool IsSessionReady()
   {
     return mIsResponderReady && mIsTransportReady;
   }
 
   virtual nsresult UntrackFromService();
 
+  void SetState(uint32_t aState)
+  {
+    if (mState == aState) {
+      return;
+    }
+
+    mState = aState;
+
+    // Notify session state change.
+    if (mListener) {
+      nsresult rv = mListener->NotifyStateChange(mSessionId, mState);
+      NS_WARN_IF(NS_FAILED(rv));
+    }
+  }
+
   nsString mUrl;
   nsString mSessionId;
   bool mIsResponderReady;
   bool mIsTransportReady;
+  uint32_t mState; // CONNECTED, CLOSED, TERMINATED
   nsCOMPtr<nsIPresentationServiceCallback> mCallback;
   nsCOMPtr<nsIPresentationSessionListener> mListener;
   nsCOMPtr<nsIPresentationDevice> mDevice;
   nsCOMPtr<nsIPresentationSessionTransport> mTransport;
   nsCOMPtr<nsIPresentationControlChannel> mControlChannel;
 };
 
 // Session info with controlling browsing context (sender side) behaviors.
--- a/dom/presentation/interfaces/nsIPresentationListener.idl
+++ b/dom/presentation/interfaces/nsIPresentationListener.idl
@@ -8,21 +8,21 @@
 interface nsIPresentationAvailabilityListener : nsISupports
 {
   /*
    * Called when device availability changes.
    */
   void notifyAvailableChange(in bool available);
 };
 
-[scriptable, uuid(3b9ae71f-2905-4969-9117-101627c1c2ea)]
+[scriptable, uuid(7dd48df8-8f8c-48c7-ac37-7b9fd1acf2f8)]
 interface nsIPresentationSessionListener : nsISupports
 {
   const unsigned short STATE_CONNECTED = 0;
-  const unsigned short STATE_DISCONNECTED = 1;
+  const unsigned short STATE_CLOSED = 1;
   const unsigned short STATE_TERMINATED = 2;
 
   /*
    * Called when session state changes.
    */
   void notifyStateChange(in DOMString sessionId,
                          in unsigned short state);
 
--- a/dom/presentation/interfaces/nsIPresentationService.idl
+++ b/dom/presentation/interfaces/nsIPresentationService.idl
@@ -28,17 +28,17 @@ interface nsIPresentationServiceCallback
   /*
    * Called when the operation fails.
    *
    * @param error: error message.
    */
   void notifyError(in nsresult error);
 };
 
-[scriptable, uuid(5a254519-45c3-4229-9b16-f58c05b78fc6)]
+[scriptable, uuid(c177a13a-bf1a-48bf-8032-d415c3343c46)]
 interface nsIPresentationService : nsISupports
 {
   /*
    * Start a new presentation session and display a prompt box which asks users
    * to select a device.
    *
    * @param url: The url of presenting page.
    * @param sessionId: An ID to identify presentation session.
@@ -58,21 +58,28 @@ interface nsIPresentationService : nsISu
    *
    * @param sessionId: An ID to identify presentation session.
    * @param stream: The message is converted to an input stream.
    */
   void sendSessionMessage(in DOMString sessionId,
                           in nsIInputStream stream);
 
   /*
+   * Close the session.
+   *
+   * @param sessionId: An ID to identify presentation session.
+   */
+  void closeSession(in DOMString sessionId);
+
+  /*
    * Terminate the session.
    *
    * @param sessionId: An ID to identify presentation session.
    */
-  void terminate(in DOMString sessionId);
+  void terminateSession(in DOMString sessionId);
 
   /*
    * Register an availability listener. Must be called from the main thread.
    *
    * @param listener: The listener to register.
    */
   void registerAvailabilityListener(in nsIPresentationAvailabilityListener listener);
 
--- a/dom/presentation/ipc/PPresentation.ipdl
+++ b/dom/presentation/ipc/PPresentation.ipdl
@@ -20,26 +20,32 @@ struct StartSessionRequest
 };
 
 struct SendSessionMessageRequest
 {
   nsString sessionId;
   InputStreamParams data;
 };
 
-struct TerminateRequest
+struct CloseSessionRequest
+{
+  nsString sessionId;
+};
+
+struct TerminateSessionRequest
 {
   nsString sessionId;
 };
 
 union PresentationIPCRequest
 {
   StartSessionRequest;
   SendSessionMessageRequest;
-  TerminateRequest;
+  CloseSessionRequest;
+  TerminateSessionRequest;
 };
 
 sync protocol PPresentation
 {
   manager PContent;
   manages PPresentationRequest;
 
 child:
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -66,21 +66,29 @@ PresentationIPCService::SendSessionMessa
   nsTArray<mozilla::ipc::FileDescriptor> fds;
   SerializeInputStream(aStream, stream, fds);
   MOZ_ASSERT(fds.IsEmpty());
 
   return SendRequest(nullptr, SendSessionMessageRequest(nsAutoString(aSessionId), stream));
 }
 
 NS_IMETHODIMP
-PresentationIPCService::Terminate(const nsAString& aSessionId)
+PresentationIPCService::CloseSession(const nsAString& aSessionId)
 {
   MOZ_ASSERT(!aSessionId.IsEmpty());
 
-  return SendRequest(nullptr, TerminateRequest(nsAutoString(aSessionId)));
+  return SendRequest(nullptr, CloseSessionRequest(nsAutoString(aSessionId)));
+}
+
+NS_IMETHODIMP
+PresentationIPCService::TerminateSession(const nsAString& aSessionId)
+{
+  MOZ_ASSERT(!aSessionId.IsEmpty());
+
+  return SendRequest(nullptr, TerminateSessionRequest(nsAutoString(aSessionId)));
 }
 
 nsresult
 PresentationIPCService::SendRequest(nsIPresentationServiceCallback* aCallback,
                                     const PresentationIPCRequest& aRequest)
 {
   if (sPresentationChild) {
     PresentationRequestChild* actor = new PresentationRequestChild(aCallback);
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -69,18 +69,21 @@ PresentationParent::RecvPPresentationReq
   nsresult rv = NS_ERROR_FAILURE;
   switch (aRequest.type()) {
     case PresentationIPCRequest::TStartSessionRequest:
       rv = actor->DoRequest(aRequest.get_StartSessionRequest());
       break;
     case PresentationIPCRequest::TSendSessionMessageRequest:
       rv = actor->DoRequest(aRequest.get_SendSessionMessageRequest());
       break;
-    case PresentationIPCRequest::TTerminateRequest:
-      rv = actor->DoRequest(aRequest.get_TerminateRequest());
+    case PresentationIPCRequest::TCloseSessionRequest:
+      rv = actor->DoRequest(aRequest.get_CloseSessionRequest());
+      break;
+    case PresentationIPCRequest::TTerminateSessionRequest:
+      rv = actor->DoRequest(aRequest.get_TerminateSessionRequest());
       break;
     default:
       MOZ_CRASH("Unknown PresentationIPCRequest type");
   }
 
   return NS_WARN_IF(NS_FAILED(rv)) ? false : true;
 }
 
@@ -273,28 +276,47 @@ PresentationRequestParent::DoRequest(con
   nsresult rv = mService->SendSessionMessage(aRequest.sessionId(), stream);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NotifyError(rv);
   }
   return NotifySuccess();
 }
 
 nsresult
-PresentationRequestParent::DoRequest(const TerminateRequest& aRequest)
+PresentationRequestParent::DoRequest(const CloseSessionRequest& aRequest)
 {
   MOZ_ASSERT(mService);
 
   // Validate the accessibility (primarily for receiver side) so that a
   // compromised child process can't fake the ID.
   if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
                   IsSessionAccessible(aRequest.sessionId(), OtherPid()))) {
     return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
   }
 
-  nsresult rv = mService->Terminate(aRequest.sessionId());
+  nsresult rv = mService->CloseSession(aRequest.sessionId());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NotifyError(rv);
+  }
+  return NotifySuccess();
+}
+
+nsresult
+PresentationRequestParent::DoRequest(const TerminateSessionRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+
+  // Validate the accessibility (primarily for receiver side) so that a
+  // compromised child process can't fake the ID.
+  if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
+                  IsSessionAccessible(aRequest.sessionId(), OtherPid()))) {
+    return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
+  }
+
+  nsresult rv = mService->TerminateSession(aRequest.sessionId());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NotifyError(rv);
   }
   return NotifySuccess();
 }
 
 NS_IMETHODIMP
 PresentationRequestParent::NotifySuccess()
--- a/dom/presentation/ipc/PresentationParent.h
+++ b/dom/presentation/ipc/PresentationParent.h
@@ -84,17 +84,19 @@ private:
   virtual ~PresentationRequestParent();
 
   nsresult SendResponse(nsresult aResult);
 
   nsresult DoRequest(const StartSessionRequest& aRequest);
 
   nsresult DoRequest(const SendSessionMessageRequest& aRequest);
 
-  nsresult DoRequest(const TerminateRequest& aRequest);
+  nsresult DoRequest(const CloseSessionRequest& aRequest);
+
+  nsresult DoRequest(const TerminateSessionRequest& aRequest);
 
   bool mActorDestroyed;
   nsCOMPtr<nsIPresentationService> mService;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/presentation/tests/mochitest/file_presentation_receiver.html
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver.html
@@ -37,22 +37,22 @@ function testSessionAvailable() {
     ok(navigator.presentation, "navigator.presentation should be available.");
     ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available.");
 
     navigator.presentation.receiver.getSession().then(
       function(aSession) {
         session = aSession;
 
         ok(session.id, "Session ID should be set: " + session.id);
-        is(session.state, "disconnected", "Session state at receiver side should be disconnected by default.");
+        is(session.state, "closed", "Session state at receiver side should be closed by default.");
         aResolve();
       },
       function(aError) {
         ok(false, "Error occurred when getting the session: " + aError);
-        teardown();
+        finish();
         aReject();
       }
     );
   });
 }
 
 function testSessionReady() {
   return new Promise(function(aResolve, aReject) {
@@ -76,29 +76,29 @@ function testIncomingMessage() {
       aResolve();
     });
 
     command({ name: 'trigger-incoming-message',
     	      data: incomingMessage });
   });
 }
 
-function testCloseSession() {
+function testTerminateSession() {
   return new Promise(function(aResolve, aReject) {
     session.onstatechange = function() {
       session.onstatechange = null;
       is(session.state, "terminated", "Session should be terminated.");
       aResolve();
     };
 
-    session.close();
+    session.terminate();
   });
 }
 
 testSessionAvailable().
 then(testSessionReady).
 then(testIncomingMessage).
-then(testCloseSession).
+then(testTerminateSession).
 then(finish);
 
 </script>
 </body>
 </html>
--- a/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html
@@ -37,22 +37,22 @@ function testSessionAvailable() {
     ok(navigator.presentation, "navigator.presentation should be available.");
     ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available.");
 
     navigator.presentation.receiver.getSession().then(
       function(aSession) {
         session = aSession;
 
         ok(session.id, "Session ID should be set: " + session.id);
-        is(session.state, "disconnected", "Session state at receiver side should be disconnected by default.");
+        is(session.state, "closed", "Session state at receiver side should be closed by default.");
         aResolve();
       },
       function(aError) {
         ok(false, "Error occurred when getting the session: " + aError);
-        teardown();
+        finish();
         aReject();
       }
     );
   });
 }
 
 function testSessionReady() {
   return new Promise(function(aResolve, aReject) {
@@ -76,29 +76,29 @@ function testIncomingMessage() {
       aResolve();
     });
 
     command({ name: 'trigger-incoming-message',
     	      data: incomingMessage });
   });
 }
 
-function testCloseSession() {
+function testTerminateSession() {
   return new Promise(function(aResolve, aReject) {
     session.onstatechange = function() {
       session.onstatechange = null;
       is(session.state, "terminated", "Session should be terminated.");
       aResolve();
     };
 
-    session.close();
+    session.terminate();
   });
 }
 
 testSessionAvailable().
 then(testSessionReady).
 then(testIncomingMessage).
-then(testCloseSession).
+then(testTerminateSession).
 then(finish);
 
 </script>
 </body>
 </html>
--- a/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html
@@ -37,22 +37,22 @@ function testSessionAvailable() {
     ok(navigator.presentation, "navigator.presentation should be available.");
     ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available.");
 
     navigator.presentation.receiver.getSession().then(
       function(aSession) {
         session = aSession;
 
         ok(session.id, "Session ID should be set: " + session.id);
-        is(session.state, "disconnected", "Session state at receiver side should be disconnected by default.");
+        is(session.state, "closed", "Session state at receiver side should be closed by default.");
         aResolve();
       },
       function(aError) {
         ok(false, "Error occurred when getting the session: " + aError);
-        teardown();
+        finish();
         aReject();
       }
     );
   });
 }
 
 function testUnexpectedControlChannelClose() {
   return new Promise(function(aResolve, aReject) {
--- a/dom/presentation/tests/mochitest/test_presentation_sender.html
+++ b/dom/presentation/tests/mochitest/test_presentation_sender.html
@@ -143,30 +143,30 @@ function testIncomingMessage() {
       is(aEvent.data, incomingMessage, "An incoming message should be received.");
       aResolve();
     });
 
     gScript.sendAsyncMessage('trigger-incoming-message', incomingMessage);
   });
 }
 
-function testCloseSession() {
+function testTerminateSession() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
       gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
       info("The data transport is closed. " + aReason);
     });
 
     session.onstatechange = function() {
       session.onstatechange = null;
       is(session.state, "terminated", "Session should be terminated.");
       aResolve();
     };
 
-    session.close();
+    session.terminate();
   });
 }
 
 function teardown() {
   gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
     gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
     gScript.destroy();
     SimpleTest.finish();
@@ -177,17 +177,17 @@ function teardown() {
 
 function runTests() {
   ok(window.PresentationRequest, "PresentationRequest should be available.");
 
   testSetup().
   then(testStartSession).
   then(testSend).
   then(testIncomingMessage).
-  then(testCloseSession).
+  then(testTerminateSession).
   then(teardown);
 }
 
 SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
   {type: 'presentation', allow: true, context: document},
--- a/dom/presentation/tests/mochitest/test_presentation_sender_default_request.html
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_default_request.html
@@ -92,30 +92,30 @@ function testStartSession() {
       aResolve();
     };
 
     // Simulate the UA triggers |start()| of the default request.
     navigator.presentation.defaultRequest.start();
   });
 }
 
-function testCloseSession() {
+function testTerminateSession() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
       gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
       info("The data transport is closed. " + aReason);
     });
 
     session.onstatechange = function() {
       session.onstatechange = null;
       is(session.state, "terminated", "Session should be terminated.");
       aResolve();
     };
 
-    session.close();
+    session.terminate();
   });
 }
 
 function teardown() {
   gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
     gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
     gScript.destroy();
     SimpleTest.finish();
@@ -125,17 +125,17 @@ function teardown() {
 }
 
 function runTests() {
   ok(window.PresentationRequest, "PresentationRequest should be available.");
   ok(navigator.presentation, "navigator.presentation should be available.");
 
   testSetup().
   then(testStartSession).
-  then(testCloseSession).
+  then(testTerminateSession).
   then(teardown);
 }
 
 SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
   {type: 'presentation', allow: true, context: document},
--- a/dom/presentation/tests/mochitest/test_presentation_sender_disconnect.html
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_disconnect.html
@@ -108,53 +108,40 @@ function testSessionDisconnection() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
       gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
       info("The data transport is closed. " + aReason);
     });
 
     session.onstatechange = function() {
       session.onstatechange = null;
-      is(session.state, "disconnected", "Session should be disconnected.");
+      is(session.state, "closed", "Session should be closed.");
       aResolve();
     };
 
     gScript.sendAsyncMessage('trigger-data-transport-close', SpecialPowers.Cr.NS_ERROR_FAILURE);
   });
 }
 
-function testCloseSession() {
-  return new Promise(function(aResolve, aReject) {
-    session.onstatechange = function() {
-      session.onstatechange = null;
-      is(session.state, "terminated", "Session should be terminated.");
-      aResolve();
-    };
-
-    session.close();
-  });
-}
-
 function teardown() {
   gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
     gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
     gScript.destroy();
     SimpleTest.finish();
   });
 
   gScript.sendAsyncMessage('teardown');
 }
 
 function runTests() {
   ok(window.PresentationRequest, "PresentationRequest should be available.");
 
   testSetup().
   then(testStartSession).
   then(testSessionDisconnection).
-  then(testCloseSession).
   then(teardown);
 }
 
 SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
   {type: 'presentation', allow: true, context: document},
--- a/dom/tv/TVSimulatorService.manifest
+++ b/dom/tv/TVSimulatorService.manifest
@@ -1,3 +1,2 @@
 component {94b065ad-d45a-436a-b394-6dabc3cf110f} TVSimulatorService.js
 contract @mozilla.org/tv/simulatorservice;1 {94b065ad-d45a-436a-b394-6dabc3cf110f}
-TVSimulatorService @mozilla.org/tv/simulatorservice;1
--- a/dom/webidl/PresentationSession.webidl
+++ b/dom/webidl/PresentationSession.webidl
@@ -5,65 +5,71 @@
  */
 
 enum PresentationSessionState
 {
   // Existing presentation, and the communication channel is active.
   "connected",
 
   // Existing presentation, but the communication channel is inactive.
-  "disconnected",
+  "closed",
 
   // The presentation is nonexistent anymore. It could be terminated manually,
-  // or either requesting page or presenting page is no longer available.
+  // or either controlling or receiving browsing context is no longer available.
   "terminated"
 };
 
 [Pref="dom.presentation.enabled",
  CheckAnyPermissions="presentation"]
 interface PresentationSession : EventTarget {
   /*
    * Unique id for all existing sessions.
    */
   [Constant]
   readonly attribute DOMString id;
 
   /*
-   * Please refer to PresentationSessionStateEvent.webidl for the declaration of
-   * PresentationSessionState.
-   *
-   * @value "connected", "disconnected", or "terminated".
+   * @value "connected", "closed", or "terminated".
    */
   readonly attribute PresentationSessionState state;
 
   /*
-   * It is called when session state changes. New state is dispatched with the
-   * event.
+   * It is called when session state changes.
    */
   attribute EventHandler onstatechange;
 
   /*
-   * After a communication channel has been established between the requesting
-   * page and the presenting page, send() is called to send message out, and the
-   * event handler "onmessage" will be invoked on the remote side.
+   * After a communication channel has been established between the controlling
+   * and receiving context, this function is called to send message out, and the
+   * event handler "onmessage" will be invoked at the remote side.
    *
-   * This function only works when state equals "connected".
+   * This function only works when the state is "connected".
    *
-   * @data: String literal-only for current implementation.
+   * TODO bug 1148307 Implement PresentationSessionTransport with DataChannel to
+   * support other binary types.
    */
   [Throws]
   void send(DOMString data);
 
   /*
    * It is triggered when receiving messages.
    */
   attribute EventHandler onmessage;
 
   /*
-   * Both the requesting page and the presenting page can close the session by
-   * calling terminate(). Then, the session is destroyed and its state is
-   * truned into "terminated". After getting into the state of "terminated",
-   * resumeSession() is incapable of re-establishing the connection.
+   * Both the controlling and receving browsing context can close the session.
+   * Then, the session state should turn into "closed".
    *
-   * This function does nothing if the state has already been "terminated".
+   * This function only works when the state is not "connected".
    */
-  void close();
+  // TODO Bug 1210340 - Support close semantics.
+  // [Throws]
+  // void close();
+
+  /*
+   * Both the controlling and receving browsing context can terminate the session.
+   * Then the session state should turn into "terminated".
+   *
+   * This function only works when the state is not "connected".
+   */
+   [Throws]
+   void terminate();
 };
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -2310,40 +2310,16 @@ nsPermissionManager::RemoveAllModifiedSi
 }
 
 NS_IMETHODIMP
 nsPermissionManager::RemovePermissionsForApp(uint32_t aAppId, bool aBrowserOnly)
 {
   ENSURE_NOT_CHILD_PROCESS;
   NS_ENSURE_ARG(aAppId != nsIScriptSecurityManager::NO_APP_ID);
 
-  // We begin by removing all the permissions from the DB.
-  // After clearing the DB, we call AddInternal() to make sure that all
-  // processes are aware of this change and the representation of the DB in
-  // memory is updated.
-  // We have to get all permissions associated with an application first
-  // because removing entries from the permissions table while iterating over
-  // it is dangerous.
-
-  nsAutoCString sql;
-  sql.AppendLiteral("DELETE FROM moz_perms WHERE appId=");
-  sql.AppendInt(aAppId);
-
-  if (aBrowserOnly) {
-    sql.AppendLiteral(" AND isInBrowserElement=1");
-  }
-
-  nsCOMPtr<mozIStorageAsyncStatement> removeStmt;
-  nsresult rv = mDBConn->CreateAsyncStatement(sql, getter_AddRefs(removeStmt));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<mozIStoragePendingStatement> pending;
-  rv = removeStmt->ExecuteAsync(nullptr, getter_AddRefs(pending));
-  NS_ENSURE_SUCCESS(rv, rv);
-
   nsCOMArray<nsIPermission> permissions;
   for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
     PermissionHashKey* entry = iter.Get();
 
     nsCOMPtr<nsIPrincipal> principal;
     nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
                                          getter_AddRefs(principal));
     if (NS_FAILED(rv)) {
new file mode 100644
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_removeforapp.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+  // initialize the permission manager service
+  let ssm = Services.scriptSecurityManager;
+  let pm = Services.perms;
+
+  function mkPrin(uri, appId, inBrowser) {
+    return ssm.createCodebasePrincipal(Services.io.newURI(uri, null, null),
+                                       {appId: appId, inBrowser: inBrowser});
+  }
+
+  function checkPerms(perms) {
+    perms.forEach((perm) => {
+      // Look up the expected permission
+      do_check_eq(pm.getPermissionObject(mkPrin(perm[0],  perm[1], perm[2]),
+                                         perm[3], true).capability,
+                  perm[4], "Permission is expected in the permission database");
+    });
+
+    // Count the entries
+    let count = 0;
+    let enumerator = Services.perms.enumerator;
+    while (enumerator.hasMoreElements()) { enumerator.getNext(); count++; }
+
+    do_check_eq(count, perms.length, "There should be the right number of permissions in the DB");
+  }
+
+  checkPerms([]);
+
+  let permissions = [
+    ['http://google.com',  1001, false, 'a', 1],
+    ['http://google.com',  1001, false, 'b', 1],
+    ['http://mozilla.com', 1001, false, 'b', 1],
+    ['http://mozilla.com', 1001, false, 'a', 1],
+
+    ['http://google.com',  1001, true, 'a', 1],
+    ['http://google.com',  1001, true, 'b', 1],
+    ['http://mozilla.com', 1001, true, 'b', 1],
+    ['http://mozilla.com', 1001, true, 'a', 1],
+
+    ['http://google.com',  1011, false, 'a', 1],
+    ['http://google.com',  1011, false, 'b', 1],
+    ['http://mozilla.com', 1011, false, 'b', 1],
+    ['http://mozilla.com', 1011, false, 'a', 1],
+  ];
+
+  permissions.forEach((perm) => {
+    pm.addFromPrincipal(mkPrin(perm[0], perm[1], perm[2]), perm[3], perm[4]);
+  });
+
+  checkPerms(permissions);
+
+  let remove_false_perms = [
+    ['http://google.com',  1011, false, 'a', 1],
+    ['http://google.com',  1011, false, 'b', 1],
+    ['http://mozilla.com', 1011, false, 'b', 1],
+    ['http://mozilla.com', 1011, false, 'a', 1],
+  ];
+
+  pm.removePermissionsForApp(1001, false);
+  checkPerms(remove_false_perms);
+
+  let restore = [
+    ['http://google.com',  1001, false, 'a', 1],
+    ['http://google.com',  1001, false, 'b', 1],
+    ['http://mozilla.com', 1001, false, 'b', 1],
+    ['http://mozilla.com', 1001, false, 'a', 1],
+
+    ['http://google.com',  1001, true, 'a', 1],
+    ['http://google.com',  1001, true, 'b', 1],
+    ['http://mozilla.com', 1001, true, 'b', 1],
+    ['http://mozilla.com', 1001, true, 'a', 1],
+  ];
+
+  restore.forEach((perm) => {
+    pm.addFromPrincipal(mkPrin(perm[0],  perm[1], perm[2]), perm[3], perm[4]);
+  });
+  checkPerms(permissions);
+
+  let remove_true_perms = [
+    ['http://google.com',  1001, false, 'a', 1],
+    ['http://google.com',  1001, false, 'b', 1],
+    ['http://mozilla.com', 1001, false, 'b', 1],
+    ['http://mozilla.com', 1001, false, 'a', 1],
+
+    ['http://google.com',  1011, false, 'a', 1],
+    ['http://google.com',  1011, false, 'b', 1],
+    ['http://mozilla.com', 1011, false, 'b', 1],
+    ['http://mozilla.com', 1011, false, 'a', 1],
+  ];
+
+  pm.removePermissionsForApp(1001, true);
+  checkPerms(remove_true_perms);
+}
--- a/extensions/cookie/test/unit/xpcshell.ini
+++ b/extensions/cookie/test/unit/xpcshell.ini
@@ -21,16 +21,17 @@ skip-if = true # Bug 863738
 [test_domain_eviction.js]
 [test_eviction.js]
 [test_permmanager_defaults.js]
 [test_permmanager_expiration.js]
 [test_permmanager_getPermissionObject.js]
 [test_permmanager_notifications.js]
 [test_permmanager_removeall.js]
 [test_permmanager_removesince.js]
+[test_permmanager_removeforapp.js]
 [test_permmanager_load_invalid_entries.js]
 skip-if = debug == true
 [test_permmanager_idn.js]
 [test_permmanager_subdomains.js]
 [test_permmanager_local_files.js]
 [test_permmanager_cleardata.js]
 [test_schema_2_migration.js]
 [test_schema_3_migration.js]
--- a/gfx/2d/FilterNodeD2D1.cpp
+++ b/gfx/2d/FilterNodeD2D1.cpp
@@ -531,16 +531,37 @@ IsTransferFilterType(FilterType aType)
     case FilterType::TABLE_TRANSFER:
     case FilterType::DISCRETE_TRANSFER:
       return true;
     default:
       return false;
   }
 }
 
+static bool
+HasUnboundedOutputRegion(FilterType aType)
+{
+  if (IsTransferFilterType(aType)) {
+    return true;
+  }
+
+  switch (aType) {
+    case FilterType::COLOR_MATRIX:
+    case FilterType::POINT_DIFFUSE:
+    case FilterType::SPOT_DIFFUSE:
+    case FilterType::DISTANT_DIFFUSE:
+    case FilterType::POINT_SPECULAR:
+    case FilterType::SPOT_SPECULAR:
+    case FilterType::DISTANT_SPECULAR:
+      return true;
+    default:
+      return false;
+  }
+}
+
 /* static */
 already_AddRefed<FilterNode>
 FilterNodeD2D1::Create(ID2D1DeviceContext *aDC, FilterType aType)
 {
   if (aType == FilterType::CONVOLVE_MATRIX) {
     return MakeAndAddRef<FilterNodeConvolveD2D1>(aDC);
   }
 
@@ -551,17 +572,17 @@ FilterNodeD2D1::Create(ID2D1DeviceContex
 
   if (FAILED(hr) || !effect) {
     gfxCriticalErrorOnce() << "Failed to create effect for FilterType: " << hexa(hr);
     return nullptr;
   }
 
   RefPtr<FilterNodeD2D1> filter = new FilterNodeD2D1(effect, aType);
 
-  if (IsTransferFilterType(aType) || aType == FilterType::COLOR_MATRIX) {
+  if (HasUnboundedOutputRegion(aType)) {
     // These filters can produce non-transparent output from transparent
     // input pixels, and we want them to have an unbounded output region.
     filter = new FilterNodeExtendInputAdapterD2D1(aDC, filter, aType);
   }
 
   if (IsTransferFilterType(aType)) {
     // Component transfer filters should appear to apply on unpremultiplied
     // colors, but the D2D1 effects apply on premultiplied colors.
--- a/gfx/2d/FilterNodeSoftware.cpp
+++ b/gfx/2d/FilterNodeSoftware.cpp
@@ -3335,17 +3335,17 @@ FilterNodeLightingSoftware<LightType, Li
   mColor = aColor;
   Invalidate();
 }
 
 template<typename LightType, typename LightingType>
 IntRect
 FilterNodeLightingSoftware<LightType, LightingType>::GetOutputRectInRect(const IntRect& aRect)
 {
-  return GetInputRectInRect(IN_LIGHTING_IN, aRect);
+  return aRect;
 }
 
 Point3D
 PointLightSoftware::GetVectorToLight(const Point3D &aTargetPoint)
 {
   return Normalized(mPosition - aTargetPoint);
 }
 
@@ -3477,17 +3477,17 @@ FilterNodeLightingSoftware<LightType, Li
                   ceil(float(aKernelUnitLengthY)));
 
   // Inflate the source rect by another pixel because the bilinear filtering in
   // ColorComponentAtPoint may want to access the margins.
   srcRect.Inflate(1);
 
   RefPtr<DataSourceSurface> input =
     GetInputDataSourceSurface(IN_LIGHTING_IN, srcRect, CAN_HANDLE_A8,
-                              EDGE_MODE_DUPLICATE);
+                              EDGE_MODE_NONE);
 
   if (!input) {
     return nullptr;
   }
 
   if (input->GetFormat() != SurfaceFormat::A8) {
     input = FilterProcessing::ExtractAlpha(input);
   }
--- a/gfx/layers/LayerScope.cpp
+++ b/gfx/layers/LayerScope.cpp
@@ -430,38 +430,41 @@ protected:
 
 #ifdef MOZ_WIDGET_GONK
 // B2G optimization.
 class DebugGLGraphicBuffer final: public DebugGLData {
 public:
     DebugGLGraphicBuffer(void *layerRef,
                          GLenum target,
                          GLuint name,
-                         const LayerRenderState &aState)
+                         const LayerRenderState &aState,
+                         bool aIsMask)
         : DebugGLData(Packet::TEXTURE),
           mLayerRef(reinterpret_cast<uint64_t>(layerRef)),
           mTarget(target),
           mName(name),
-          mState(aState)
+          mState(aState),
+          mIsMask(aIsMask)
     {
     }
 
     virtual bool Write() override {
         return WriteToStream(mPacket);
     }
 
     bool TryPack(bool packData) {
         android::sp<android::GraphicBuffer> buffer = mState.mSurface;
         MOZ_ASSERT(buffer.get());
 
         mPacket.set_type(mDataType);
         TexturePacket* tp = mPacket.mutable_texture();
         tp->set_layerref(mLayerRef);
         tp->set_name(mName);
         tp->set_target(mTarget);
+        tp->set_ismask(mIsMask);
 
         int pFormat = buffer->getPixelFormat();
         if (HAL_PIXEL_FORMAT_RGBA_8888 != pFormat &&
             HAL_PIXEL_FORMAT_RGBX_8888 != pFormat) {
             return false;
         }
 
         int32_t stride = buffer->getStride() * 4;
@@ -508,33 +511,36 @@ public:
         return true;
     }
 
 private:
     uint64_t mLayerRef;
     GLenum mTarget;
     GLuint mName;
     const LayerRenderState &mState;
+    bool mIsMask;
     Packet mPacket;
 };
 #endif
 
 class DebugGLTextureData final: public DebugGLData {
 public:
     DebugGLTextureData(GLContext* cx,
                        void* layerRef,
                        GLenum target,
                        GLuint name,
-                       DataSourceSurface* img)
+                       DataSourceSurface* img,
+                       bool aIsMask)
         : DebugGLData(Packet::TEXTURE),
           mLayerRef(reinterpret_cast<uint64_t>(layerRef)),
           mTarget(target),
           mName(name),
           mContextAddress(reinterpret_cast<intptr_t>(cx)),
-          mDatasize(0)
+          mDatasize(0),
+          mIsMask(aIsMask)
     {
         // pre-packing
         // DataSourceSurface may have locked buffer,
         // so we should compress now, and then it could
         // be unlocked outside.
         pack(img);
     }
 
@@ -547,16 +553,17 @@ private:
         mPacket.set_type(mDataType);
 
         TexturePacket* tp = mPacket.mutable_texture();
         tp->set_layerref(mLayerRef);
         tp->set_name(mName);
         tp->set_target(mTarget);
         tp->set_dataformat(LOCAL_GL_RGBA);
         tp->set_glcontext(static_cast<uint64_t>(mContextAddress));
+        tp->set_ismask(mIsMask);
 
         if (aImage) {
             tp->set_width(aImage->GetSize().width);
             tp->set_height(aImage->GetSize().height);
             tp->set_stride(aImage->Stride());
 
             mDatasize = aImage->GetSize().height * aImage->Stride();
 
@@ -585,16 +592,17 @@ private:
     }
 
 protected:
     uint64_t mLayerRef;
     GLenum mTarget;
     GLuint mName;
     intptr_t mContextAddress;
     uint32_t mDatasize;
+    bool mIsMask;
 
     // Packet data
     Packet mPacket;
 };
 
 class DebugGLColorData final: public DebugGLData {
 public:
     DebugGLColorData(void* layerRef,
@@ -867,19 +875,21 @@ NS_IMPL_ISUPPORTS(DebugDataSender::SendT
 
 
 /*
  * LayerScope SendXXX Structure
  * 1. SendLayer
  * 2. SendEffectChain
  *   1. SendTexturedEffect
  *      -> SendTextureSource
- *   2. SendYCbCrEffect
+ *   2. SendMaskEffect
  *      -> SendTextureSource
- *   3. SendColor
+ *   3. SendYCbCrEffect
+ *      -> SendTextureSource
+ *   4. SendColor
  */
 class SenderHelper
 {
 // Sender public APIs
 public:
     static void SendLayer(LayerComposite* aLayer,
                           int aWidth,
                           int aHeight);
@@ -890,80 +900,74 @@ public:
                                 int aHeight = 0);
 
     static void SetLayersTreeSendable(bool aSet) {sLayersTreeSendable = aSet;}
 
     static void SetLayersBufferSendable(bool aSet) {sLayersBufferSendable = aSet;}
 
     static bool GetLayersTreeSendable() {return sLayersTreeSendable;}
 
-    static void ClearTextureIdList();
+    static void ClearSentTextureIds();
 
 
 // Sender private functions
 private:
     static void SendColor(void* aLayerRef,
                           const Color& aColor,
                           int aWidth,
                           int aHeight);
     static void SendTextureSource(GLContext* aGLContext,
                                   void* aLayerRef,
                                   TextureSourceOGL* aSource,
-                                  GLuint aTexID,
-                                  bool aFlipY);
+                                  bool aFlipY,
+                                  bool aIsMask);
 #ifdef MOZ_WIDGET_GONK
-    static bool SendGraphicBuffer(void* aLayerRef,
+    static bool SendGraphicBuffer(GLContext* aGLContext,
+                                  void* aLayerRef,
                                   TextureSourceOGL* aSource,
-                                  GLuint aTexID,
-                                  const TexturedEffect* aEffect);
+                                  const TexturedEffect* aEffect,
+                                  bool aIsMask);
 #endif
     static void SendTexturedEffect(GLContext* aGLContext,
                                    void* aLayerRef,
                                    const TexturedEffect* aEffect);
+    static void SendMaskEffect(GLContext* aGLContext,
+                                   void* aLayerRef,
+                                   const EffectMask* aEffect);
     static void SendYCbCrEffect(GLContext* aGLContext,
                                 void* aLayerRef,
                                 const EffectYCbCr* aEffect);
     static GLuint GetTextureID(GLContext* aGLContext,
                                TextureSourceOGL* aSource);
-    static bool IsTextureIdContainsInList(GLuint aTextureId);
+    static bool HasTextureIdBeenSent(GLuint aTextureId);
 // Data fields
 private:
     static bool sLayersTreeSendable;
     static bool sLayersBufferSendable;
-    static std::list<GLuint> sTextureIdList;
+    static std::vector<GLuint> sSentTextureIds;
 };
 
 bool SenderHelper::sLayersTreeSendable = true;
 bool SenderHelper::sLayersBufferSendable = true;
-std::list<GLuint> SenderHelper::sTextureIdList;
+std::vector<GLuint> SenderHelper::sSentTextureIds;
 
 
 // ----------------------------------------------
 // SenderHelper implementation
 // ----------------------------------------------
 void
-SenderHelper::ClearTextureIdList()
+SenderHelper::ClearSentTextureIds()
 {
-    std::list<GLuint>::iterator it;
-    while (!sTextureIdList.empty()) {
-        it = sTextureIdList.begin();
-        sTextureIdList.erase(it);
-    }
+    sSentTextureIds.clear();
 }
 
 bool
-SenderHelper::IsTextureIdContainsInList(GLuint aTextureId)
+SenderHelper::HasTextureIdBeenSent(GLuint aTextureId)
 {
-    for (std::list<GLuint>::iterator it = sTextureIdList.begin();
-         it != sTextureIdList.end(); ++it) {
-        if (*it == aTextureId) {
-          return true;
-        }
-    }
-    return false;
+    return std::find(sSentTextureIds.begin(), sSentTextureIds.end(), aTextureId) != sSentTextureIds.end();
 }
 
 void
 SenderHelper::SendLayer(LayerComposite* aLayer,
                         int aWidth,
                         int aHeight)
 {
     MOZ_ASSERT(aLayer && aLayer->GetLayer());
@@ -1030,132 +1034,138 @@ SenderHelper::GetTextureID(GLContext* aG
 
     return texID;
 }
 
 void
 SenderHelper::SendTextureSource(GLContext* aGLContext,
                                 void* aLayerRef,
                                 TextureSourceOGL* aSource,
-                                GLuint aTexID,
-                                bool aFlipY)
+                                bool aFlipY,
+                                bool aIsMask)
 {
     MOZ_ASSERT(aGLContext);
     if (!aGLContext) {
         return;
     }
+    GLuint texID = GetTextureID(aGLContext, aSource);
+    if (HasTextureIdBeenSent(texID)) {
+        return;
+    }
 
     GLenum textureTarget = aSource->GetTextureTarget();
     ShaderConfigOGL config = ShaderConfigFromTargetAndFormat(textureTarget,
                                                              aSource->GetFormat());
     int shaderConfig = config.mFeatures;
 
     gfx::IntSize size = aSource->GetSize();
 
     // By sending 0 to ReadTextureImage rely upon aSource->BindTexture binding
     // texture correctly. texID is used for tracking in DebugGLTextureData.
     RefPtr<DataSourceSurface> img =
         aGLContext->ReadTexImageHelper()->ReadTexImage(0, textureTarget,
                                                          size,
                                                          shaderConfig, aFlipY);
     gLayerScopeManager.GetSocketManager()->AppendDebugData(
         new DebugGLTextureData(aGLContext, aLayerRef, textureTarget,
-                               aTexID, img));
+                               texID, img, aIsMask));
 
-    sTextureIdList.push_back(aTexID);
-    gLayerScopeManager.CurrentSession().mTexIDs.push_back(aTexID);
+    sSentTextureIds.push_back(texID);
+    gLayerScopeManager.CurrentSession().mTexIDs.push_back(texID);
 
 }
 
 #ifdef MOZ_WIDGET_GONK
 bool
-SenderHelper::SendGraphicBuffer(void* aLayerRef,
+SenderHelper::SendGraphicBuffer(GLContext* aGLContext,
+                                void* aLayerRef,
                                 TextureSourceOGL* aSource,
-                                GLuint aTexID,
-                                const TexturedEffect* aEffect) {
+                                const TexturedEffect* aEffect,
+                                bool aIsMask) {
+    GLuint texID = GetTextureID(aGLContext, aSource);
+    if (HasTextureIdBeenSent(texID)) {
+        return false;
+    }
     if (!aEffect->mState.mSurface.get()) {
         return false;
     }
 
     GLenum target = aSource->GetTextureTarget();
     mozilla::UniquePtr<DebugGLGraphicBuffer> package =
-        MakeUnique<DebugGLGraphicBuffer>(aLayerRef, target, aTexID, aEffect->mState);
+        MakeUnique<DebugGLGraphicBuffer>(aLayerRef, target, texID, aEffect->mState, aIsMask);
 
     // The texure content in this TexureHost is not altered,
     // we don't need to send it again.
     bool changed = gLayerScopeManager.GetContentMonitor()->IsChangedOrNew(
         aEffect->mState.mTexture);
     if (!package->TryPack(changed)) {
         return false;
     }
 
     // Transfer ownership to SocketManager.
     gLayerScopeManager.GetSocketManager()->AppendDebugData(package.release());
-    sTextureIdList.push_back(aTexID);
+    sSentTextureIds.push_back(texID);
 
-    gLayerScopeManager.CurrentSession().mTexIDs.push_back(aTexID);
+    gLayerScopeManager.CurrentSession().mTexIDs.push_back(texID);
 
     gLayerScopeManager.GetContentMonitor()->ClearChangedHost(aEffect->mState.mTexture);
     return true;
 }
 #endif
 
 void
 SenderHelper::SendTexturedEffect(GLContext* aGLContext,
                                  void* aLayerRef,
                                  const TexturedEffect* aEffect)
 {
     TextureSourceOGL* source = aEffect->mTexture->AsSourceOGL();
     if (!source) {
         return;
     }
 
-    GLuint texID = GetTextureID(aGLContext, source);
-    if (IsTextureIdContainsInList(texID)) {
-        return;
-    }
-
 #ifdef MOZ_WIDGET_GONK
-    if (SendGraphicBuffer(aLayerRef, source, texID, aEffect)) {
+    if (SendGraphicBuffer(aGLContext, aLayerRef, source, aEffect, false)) {
         return;
     }
 #endif
     // Fallback texture sending path.
     // Render to texture and read pixels back.
-    SendTextureSource(aGLContext, aLayerRef, source, texID, false);
+    SendTextureSource(aGLContext, aLayerRef, source, false, false);
+}
+
+void
+SenderHelper::SendMaskEffect(GLContext* aGLContext,
+                                 void* aLayerRef,
+                                 const EffectMask* aEffect)
+{
+    TextureSourceOGL* source = aEffect->mMaskTexture->AsSourceOGL();
+    if (!source) {
+        return;
+    }
+
+    SendTextureSource(aGLContext, aLayerRef, source, false, true);
 }
 
 void
 SenderHelper::SendYCbCrEffect(GLContext* aGLContext,
                               void* aLayerRef,
                               const EffectYCbCr* aEffect)
 {
     TextureSource* sourceYCbCr = aEffect->mTexture;
     if (!sourceYCbCr)
         return;
 
     const int Y = 0, Cb = 1, Cr = 2;
     TextureSourceOGL* sourceY =  sourceYCbCr->GetSubSource(Y)->AsSourceOGL();
     TextureSourceOGL* sourceCb = sourceYCbCr->GetSubSource(Cb)->AsSourceOGL();
     TextureSourceOGL* sourceCr = sourceYCbCr->GetSubSource(Cr)->AsSourceOGL();
 
-    GLuint texID = GetTextureID(aGLContext, sourceY);
-    if (!IsTextureIdContainsInList(texID)) {
-        SendTextureSource(aGLContext, aLayerRef, sourceY, texID, false);
-    }
-
-    texID = GetTextureID(aGLContext, sourceCb);
-    if (!IsTextureIdContainsInList(texID)) {
-        SendTextureSource(aGLContext, aLayerRef, sourceCb, texID, false);
-    }
-
-    texID = GetTextureID(aGLContext, sourceCr);
-    if (!IsTextureIdContainsInList(texID)) {
-        SendTextureSource(aGLContext, aLayerRef, sourceCr, texID, false);
-    }
+    SendTextureSource(aGLContext, aLayerRef, sourceY, false, false);
+    SendTextureSource(aGLContext, aLayerRef, sourceCb, false, false);
+    SendTextureSource(aGLContext, aLayerRef, sourceCr, false, false);
 }
 
 void
 SenderHelper::SendEffectChain(GLContext* aGLContext,
                               const EffectChain& aEffectChain,
                               int aWidth,
                               int aHeight)
 {
@@ -1183,18 +1193,21 @@ SenderHelper::SendEffectChain(GLContext*
             break;
         }
         case EffectTypes::COMPONENT_ALPHA:
         case EffectTypes::RENDER_TARGET:
         default:
             break;
     }
 
-    //const Effect* secondaryEffect = aEffectChain.mSecondaryEffects[EffectTypes::MASK];
-    // TODO:
+    if (aEffectChain.mSecondaryEffects[EffectTypes::MASK]) {
+        const EffectMask* effectMask =
+            static_cast<const EffectMask*>(aEffectChain.mSecondaryEffects[EffectTypes::MASK].get());
+        SendMaskEffect(aGLContext, aEffectChain.mLayerRef, effectMask);
+    }
 }
 
 void
 LayerScope::ContentChanged(TextureHost *host)
 {
     if (!CheckSendable()) {
       return;
     }
@@ -1867,17 +1880,17 @@ LayerScopeAutoFrame::~LayerScopeAutoFram
 }
 
 void
 LayerScopeAutoFrame::BeginFrame(int64_t aFrameStamp)
 {
     if (!LayerScope::CheckSendable()) {
         return;
     }
-    SenderHelper::ClearTextureIdList();
+    SenderHelper::ClearSentTextureIds();
 
     gLayerScopeManager.GetSocketManager()->AppendDebugData(
         new DebugGLFrameStatusData(Packet::FRAMESTART, aFrameStamp));
 }
 
 void
 LayerScopeAutoFrame::EndFrame()
 {
--- a/gfx/layers/TextureDIB.cpp
+++ b/gfx/layers/TextureDIB.cpp
@@ -48,16 +48,20 @@ TextureClientDIB::BorrowDrawTarget()
 {
   MOZ_ASSERT(mIsLocked && IsAllocated());
 
   if (!mDrawTarget) {
     mDrawTarget =
       gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(mSurface, mSize);
   }
 
+  if (!mDrawTarget) {
+    gfxCriticalNote << "DIB failed draw target surface " << mSize << ", " << (int)mIsLocked << ", " << IsAllocated();
+  }
+
   return mDrawTarget;
 }
 
 void
 TextureClientDIB::UpdateFromSurface(gfx::SourceSurface* aSurface)
 {
   MOZ_ASSERT(mIsLocked && IsAllocated());
 
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -419,17 +419,17 @@ TextureClientD3D11::BorrowDrawTarget()
   if (mTexture) {
     mDrawTarget = Factory::CreateDrawTargetForD3D11Texture(mTexture, mFormat);
   } else
   {
     MOZ_ASSERT(mTexture10);
     mDrawTarget = Factory::CreateDrawTargetForD3D10Texture(mTexture10, mFormat);
   }
   if (!mDrawTarget) {
-      gfxWarning() << "Invalid draw target for borrowing";
+    gfxCriticalNote << "Invalid draw target for borrowing D3D11 " << (int)mFormat;
   }
   return mDrawTarget;
 }
 
 void
 TextureClientD3D11::UpdateFromSurface(gfx::SourceSurface* aSurface)
 {
   RefPtr<DataSourceSurface> srcSurf = aSurface->GetDataSurface();
--- a/gfx/layers/d3d9/TextureD3D9.cpp
+++ b/gfx/layers/d3d9/TextureD3D9.cpp
@@ -536,44 +536,52 @@ TextureClientD3D9::ToSurfaceDescriptor(S
 }
 
 gfx::DrawTarget*
 TextureClientD3D9::BorrowDrawTarget()
 {
   MOZ_ASSERT(mIsLocked && mD3D9Surface);
   if (!mIsLocked || !mD3D9Surface) {
     NS_WARNING("Calling BorrowDrawTarget on an Unlocked TextureClient");
+    gfxCriticalNote << "BorrowDrawTarget on an Unlocked TextureClient";
     return nullptr;
   }
 
   if (mDrawTarget) {
     return mDrawTarget;
   }
 
   if (ContentForFormat(mFormat) == gfxContentType::COLOR) {
     nsRefPtr<gfxASurface> surface = new gfxWindowsSurface(mD3D9Surface);
     if (!surface || surface->CairoStatus()) {
       NS_WARNING("Could not create surface for d3d9 surface");
+      gfxCriticalNote << "Failed creation on D3D9";
       return nullptr;
     }
     mDrawTarget =
       gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surface, mSize);
+    if (!mDrawTarget) {
+      gfxCriticalNote << "Bad draw target creation for surface D3D9 " << mSize;
+    }
   } else {
     // gfxWindowsSurface don't support transparency so we can't use the d3d9
     // windows surface optimization.
     // Instead we have to use a gfxImageSurface and fallback for font drawing.
     D3DLOCKED_RECT rect;
     HRESULT hr = mD3D9Surface->LockRect(&rect, nullptr, 0);
     if (FAILED(hr) || !rect.pBits) {
       gfxCriticalError() << "Failed to lock rect borrowing the target in D3D9 " << hexa(hr);
       return nullptr;
     }
     mDrawTarget =
      gfxPlatform::GetPlatform()->CreateDrawTargetForData((uint8_t*)rect.pBits, mSize,
                                                          rect.Pitch, mFormat);
+    if (!mDrawTarget) {
+      gfxCriticalNote << "Bad draw target creation for data D3D9 " << mSize << ", " << (int)mFormat;
+    }
     mLockRect = true;
   }
 
   if (mNeedsClear) {
     mDrawTarget->ClearRect(Rect(0, 0, GetSize().width, GetSize().height));
     mNeedsClear = false;
   }
   if (mNeedsClearWhite) {
--- a/gfx/layers/protobuf/LayerScopePacket.pb.cc
+++ b/gfx/layers/protobuf/LayerScopePacket.pb.cc
@@ -643,16 +643,17 @@ const int TexturePacket::kLayerrefFieldN
 const int TexturePacket::kWidthFieldNumber;
 const int TexturePacket::kHeightFieldNumber;
 const int TexturePacket::kStrideFieldNumber;
 const int TexturePacket::kNameFieldNumber;
 const int TexturePacket::kTargetFieldNumber;
 const int TexturePacket::kDataformatFieldNumber;
 const int TexturePacket::kGlcontextFieldNumber;
 const int TexturePacket::kDataFieldNumber;
+const int TexturePacket::kIsMaskFieldNumber;
 #endif  // !_MSC_VER
 
 TexturePacket::TexturePacket()
   : ::google::protobuf::MessageLite() {
   SharedCtor();
   // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.TexturePacket)
 }
 
@@ -673,16 +674,17 @@ void TexturePacket::SharedCtor() {
   width_ = 0u;
   height_ = 0u;
   stride_ = 0u;
   name_ = 0u;
   target_ = 0u;
   dataformat_ = 0u;
   glcontext_ = GOOGLE_ULONGLONG(0);
   data_ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
+  ismask_ = false;
   ::memset(_has_bits_, 0, sizeof(_has_bits_));
 }
 
 TexturePacket::~TexturePacket() {
   // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.TexturePacket)
   SharedDtor();
 }
 
@@ -727,20 +729,23 @@ void TexturePacket::Clear() {
     size_t f = OFFSET_OF_FIELD_(first);                    \
     size_t n = OFFSET_OF_FIELD_(last) - f + sizeof(last);  \
     ::memset(&first, 0, n);                                \
   } while (0)
 
   if (_has_bits_[0 / 32] & 255) {
     ZR_(layerref_, glcontext_);
   }
-  if (has_data()) {
-    if (data_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
-      data_->clear();
+  if (_has_bits_[8 / 32] & 768) {
+    if (has_data()) {
+      if (data_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
+        data_->clear();
+      }
     }
+    ismask_ = false;
   }
 
 #undef OFFSET_OF_FIELD_
 #undef ZR_
 
   ::memset(_has_bits_, 0, sizeof(_has_bits_));
   mutable_unknown_fields()->clear();
 }
@@ -750,17 +755,17 @@ bool TexturePacket::MergePartialFromCode
 #define DO_(EXPRESSION) if (!(EXPRESSION)) goto failure
   ::google::protobuf::uint32 tag;
   ::google::protobuf::io::StringOutputStream unknown_fields_string(
       mutable_unknown_fields());
   ::google::protobuf::io::CodedOutputStream unknown_fields_stream(
       &unknown_fields_string);
   // @@protoc_insertion_point(parse_start:mozilla.layers.layerscope.TexturePacket)
   for (;;) {
-    ::std::pair< ::google::protobuf::uint32, bool> p = input->ReadTagWithCutoff(127);
+    ::std::pair< ::google::protobuf::uint32, bool> p = input->ReadTagWithCutoff(16383);
     tag = p.first;
     if (!p.second) goto handle_unusual;
     switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
       // required uint64 layerref = 1;
       case 1: {
         if (tag == 8) {
           DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
                    ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
@@ -882,16 +887,31 @@ bool TexturePacket::MergePartialFromCode
       case 9: {
         if (tag == 74) {
          parse_data:
           DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
                 input, this->mutable_data()));
         } else {
           goto handle_unusual;
         }
+        if (input->ExpectTag(160)) goto parse_isMask;
+        break;
+      }
+
+      // optional bool isMask = 20;
+      case 20: {
+        if (tag == 160) {
+         parse_isMask:
+          DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+                   bool, ::google::protobuf::internal::WireFormatLite::TYPE_BOOL>(
+                 input, &ismask_)));
+          set_has_ismask();
+        } else {
+          goto handle_unusual;
+        }
         if (input->ExpectAtEnd()) goto success;
         break;
       }
 
       default: {
       handle_unusual:
         if (tag == 0 ||
             ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
@@ -957,16 +977,21 @@ void TexturePacket::SerializeWithCachedS
   }
 
   // optional bytes data = 9;
   if (has_data()) {
     ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
       9, this->data(), output);
   }
 
+  // optional bool isMask = 20;
+  if (has_ismask()) {
+    ::google::protobuf::internal::WireFormatLite::WriteBool(20, this->ismask(), output);
+  }
+
   output->WriteRaw(unknown_fields().data(),
                    unknown_fields().size());
   // @@protoc_insertion_point(serialize_end:mozilla.layers.layerscope.TexturePacket)
 }
 
 int TexturePacket::ByteSize() const {
   int total_size = 0;
 
@@ -1031,16 +1056,21 @@ int TexturePacket::ByteSize() const {
   if (_has_bits_[8 / 32] & (0xffu << (8 % 32))) {
     // optional bytes data = 9;
     if (has_data()) {
       total_size += 1 +
         ::google::protobuf::internal::WireFormatLite::BytesSize(
           this->data());
     }
 
+    // optional bool isMask = 20;
+    if (has_ismask()) {
+      total_size += 2 + 1;
+    }
+
   }
   total_size += unknown_fields().size();
 
   GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
   _cached_size_ = total_size;
   GOOGLE_SAFE_CONCURRENT_WRITES_END();
   return total_size;
 }
@@ -1077,16 +1107,19 @@ void TexturePacket::MergeFrom(const Text
     if (from.has_glcontext()) {
       set_glcontext(from.glcontext());
     }
   }
   if (from._has_bits_[8 / 32] & (0xffu << (8 % 32))) {
     if (from.has_data()) {
       set_data(from.data());
     }
+    if (from.has_ismask()) {
+      set_ismask(from.ismask());
+    }
   }
   mutable_unknown_fields()->append(from.unknown_fields());
 }
 
 void TexturePacket::CopyFrom(const TexturePacket& from) {
   if (&from == this) return;
   Clear();
   MergeFrom(from);
@@ -1104,16 +1137,17 @@ void TexturePacket::Swap(TexturePacket* 
     std::swap(width_, other->width_);
     std::swap(height_, other->height_);
     std::swap(stride_, other->stride_);
     std::swap(name_, other->name_);
     std::swap(target_, other->target_);
     std::swap(dataformat_, other->dataformat_);
     std::swap(glcontext_, other->glcontext_);
     std::swap(data_, other->data_);
+    std::swap(ismask_, other->ismask_);
     std::swap(_has_bits_[0], other->_has_bits_[0]);
     _unknown_fields_.swap(other->_unknown_fields_);
     std::swap(_cached_size_, other->_cached_size_);
   }
 }
 
 ::std::string TexturePacket::GetTypeName() const {
   return "mozilla.layers.layerscope.TexturePacket";
--- a/gfx/layers/protobuf/LayerScopePacket.pb.h
+++ b/gfx/layers/protobuf/LayerScopePacket.pb.h
@@ -455,16 +455,23 @@ class TexturePacket : public ::google::p
   inline const ::std::string& data() const;
   inline void set_data(const ::std::string& value);
   inline void set_data(const char* value);
   inline void set_data(const void* value, size_t size);
   inline ::std::string* mutable_data();
   inline ::std::string* release_data();
   inline void set_allocated_data(::std::string* data);
 
+  // optional bool isMask = 20;
+  inline bool has_ismask() const;
+  inline void clear_ismask();
+  static const int kIsMaskFieldNumber = 20;
+  inline bool ismask() const;
+  inline void set_ismask(bool value);
+
   // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.TexturePacket)
  private:
   inline void set_has_layerref();
   inline void clear_has_layerref();
   inline void set_has_width();
   inline void clear_has_width();
   inline void set_has_height();
   inline void clear_has_height();
@@ -475,30 +482,33 @@ class TexturePacket : public ::google::p
   inline void set_has_target();
   inline void clear_has_target();
   inline void set_has_dataformat();
   inline void clear_has_dataformat();
   inline void set_has_glcontext();
   inline void clear_has_glcontext();
   inline void set_has_data();
   inline void clear_has_data();
+  inline void set_has_ismask();
+  inline void clear_has_ismask();
 
   ::std::string _unknown_fields_;
 
   ::google::protobuf::uint32 _has_bits_[1];
   mutable int _cached_size_;
   ::google::protobuf::uint64 layerref_;
   ::google::protobuf::uint32 width_;
   ::google::protobuf::uint32 height_;
   ::google::protobuf::uint32 stride_;
   ::google::protobuf::uint32 name_;
   ::google::protobuf::uint32 target_;
   ::google::protobuf::uint32 dataformat_;
   ::google::protobuf::uint64 glcontext_;
   ::std::string* data_;
+  bool ismask_;
   #ifdef GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER
   friend void  protobuf_AddDesc_LayerScopePacket_2eproto_impl();
   #else
   friend void  protobuf_AddDesc_LayerScopePacket_2eproto();
   #endif
   friend void protobuf_AssignDesc_LayerScopePacket_2eproto();
   friend void protobuf_ShutdownFile_LayerScopePacket_2eproto();
 
@@ -2659,16 +2669,40 @@ inline void TexturePacket::set_allocated
     data_ = data;
   } else {
     clear_has_data();
     data_ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
   }
   // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.TexturePacket.data)
 }
 
+// optional bool isMask = 20;
+inline bool TexturePacket::has_ismask() const {
+  return (_has_bits_[0] & 0x00000200u) != 0;
+}
+inline void TexturePacket::set_has_ismask() {
+  _has_bits_[0] |= 0x00000200u;
+}
+inline void TexturePacket::clear_has_ismask() {
+  _has_bits_[0] &= ~0x00000200u;
+}
+inline void TexturePacket::clear_ismask() {
+  ismask_ = false;
+  clear_has_ismask();
+}
+inline bool TexturePacket::ismask() const {
+  // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.isMask)
+  return ismask_;
+}
+inline void TexturePacket::set_ismask(bool value) {
+  set_has_ismask();
+  ismask_ = value;
+  // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.isMask)
+}
+
 // -------------------------------------------------------------------
 
 // LayersPacket_Layer_Size
 
 // optional int32 w = 1;
 inline bool LayersPacket_Layer_Size::has_w() const {
   return (_has_bits_[0] & 0x00000001u) != 0;
 }
--- a/gfx/layers/protobuf/LayerScopePacket.proto
+++ b/gfx/layers/protobuf/LayerScopePacket.proto
@@ -15,25 +15,33 @@ message FramePacket {
 message ColorPacket {
   required uint64 layerref = 1;
   optional uint32 width = 2;
   optional uint32 height = 3;
   optional uint32 color = 4;
 }
 
 message TexturePacket {
+  // Basic info
   required uint64 layerref = 1;
   optional uint32 width = 2;
   optional uint32 height = 3;
   optional uint32 stride = 4;
   optional uint32 name = 5;
   optional uint32 target = 6;
   optional uint32 dataformat = 7;
   optional uint64 glcontext = 8;
   optional bytes data = 9;
+
+  // Texture effect attributes (10 to 19)
+  // TODO: reserved for primary textured effect attributes, see Bug 1205521
+
+  // Mask effect attributes (20 to 29)
+  optional bool isMask = 20;
+  // TODO: reserved for secondary mask effect attributes, see Bug 1205521
 }
 
 message LayersPacket {
   message Layer {
     enum LayerType {
       UnknownLayer = 0;
       LayerManager = 1;
       ContainerLayer = 2;
--- a/gfx/src/FilterSupport.cpp
+++ b/gfx/src/FilterSupport.cpp
@@ -1227,19 +1227,16 @@ FilterNodeGraphFromDescription(DrawTarge
                                SourceSurface* aFillPaint,
                                const IntRect& aFillPaintRect,
                                SourceSurface* aStrokePaint,
                                const IntRect& aStrokePaintRect,
                                nsTArray<RefPtr<SourceSurface>>& aAdditionalImages)
 {
   const nsTArray<FilterPrimitiveDescription>& primitives = aFilter.mPrimitives;
 
-  Rect resultNeededRect(aResultNeededRect);
-  resultNeededRect.RoundOut();
-
   RefPtr<FilterCachedColorModels> sourceFilters[4];
   nsTArray<RefPtr<FilterCachedColorModels> > primitiveFilters;
 
   for (size_t i = 0; i < primitives.Length(); ++i) {
     const FilterPrimitiveDescription& descr = primitives[i];
 
     nsTArray<RefPtr<FilterNode> > inputFilterNodes;
     nsTArray<IntRect> inputSourceRects;
@@ -1402,16 +1399,19 @@ ResultChangeRegionForPrimitive(const Fil
       return aInputChangeRegions[0].Inflated(nsIntMargin(ry, rx, ry, rx));
     }
 
     case PrimitiveType::Tile:
       return aDescription.PrimitiveSubregion();
 
     case PrimitiveType::ConvolveMatrix:
     {
+      if (atts.GetUint(eConvolveMatrixEdgeMode) != EDGE_MODE_NONE) {
+        return aDescription.PrimitiveSubregion();
+      }
       Size kernelUnitLength = atts.GetSize(eConvolveMatrixKernelUnitLength);
       IntSize kernelSize = atts.GetIntSize(eConvolveMatrixKernelSize);
       IntPoint target = atts.GetIntPoint(eConvolveMatrixTarget);
       nsIntMargin m(ceil(kernelUnitLength.width * (target.x)),
                     ceil(kernelUnitLength.height * (target.y)),
                     ceil(kernelUnitLength.width * (kernelSize.width - target.x - 1)),
                     ceil(kernelUnitLength.height * (kernelSize.height - target.y - 1)));
       return aInputChangeRegions[0].Inflated(m);
@@ -1598,16 +1598,18 @@ FilterSupport::PostFilterExtentsForPrimi
       if (ResultOfZeroUnderTransferFunction(functionAttributes) > 0.0f) {
         return aDescription.PrimitiveSubregion();
       }
       return aInputExtents[0];
     }
 
     case PrimitiveType::Turbulence:
     case PrimitiveType::Image:
+    case PrimitiveType::DiffuseLighting:
+    case PrimitiveType::SpecularLighting:
     {
       return aDescription.PrimitiveSubregion();
     }
 
     case PrimitiveType::Morphology:
     {
       uint32_t op = atts.GetUint(eMorphologyOperator);
       if (op == SVG_OPERATOR_ERODE) {
--- a/gfx/src/FilterSupport.h
+++ b/gfx/src/FilterSupport.h
@@ -432,17 +432,18 @@ public:
                           SourceSurface* aStrokePaint,
                           const IntRect& aStrokePaintRect,
                           nsTArray<RefPtr<SourceSurface>>& aAdditionalImages,
                           const Point& aDestPoint,
                           const DrawOptions& aOptions = DrawOptions());
 
   /**
    * Computes the region that changes in the filter output due to a change in
-   * input.
+   * input.  This is primarily needed when an individual piece of content inside
+   * a filtered container element changes.
    */
   static nsIntRegion
   ComputeResultChangeRegion(const FilterDescription& aFilter,
                             const nsIntRegion& aSourceGraphicChange,
                             const nsIntRegion& aFillPaintChange,
                             const nsIntRegion& aStrokePaintChange);
 
   /**
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -176,25 +176,20 @@ static inline bool IsSimulatedOOMAllocat
 static inline bool ShouldFailWithOOM() { return false; }
 } /* namespace oom */
 } /* namespace js */
 
 # endif /* DEBUG || JS_OOM_BREAKPOINT */
 
 namespace js {
 
-MOZ_NORETURN MOZ_COLD void
-CrashAtUnhandlableOOM(const char* reason);
-
 /* Disable OOM testing in sections which are not OOM safe. */
 struct MOZ_RAII AutoEnterOOMUnsafeRegion
 {
-    void crash(const char* reason) {
-        CrashAtUnhandlableOOM(reason);
-    }
+    MOZ_NORETURN MOZ_COLD void crash(const char* reason);
 
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
     AutoEnterOOMUnsafeRegion()
       : oomEnabled_(oom::IsThreadSimulatingOOM() && OOM_maxAllocations != UINT32_MAX),
         oomAfter_(0)
     {
         if (oomEnabled_) {
             oomAfter_ = int64_t(OOM_maxAllocations) - OOM_counter;
--- a/js/src/builtin/Module.js
+++ b/js/src/builtin/Module.js
@@ -92,22 +92,22 @@ function ModuleDeclarationInstantiation(
 {
     if (!IsObject(this) || !IsModule(this))
         return callFunction(CallModuleMethodIfWrapped, this, "ModuleDeclarationInstantiation");
 
     // Step 1
     let module = this;
 
     // Step 5
-    if (module.environment !== undefined)
+    if (GetModuleEnvironment(module) !== undefined)
         return;
 
     // Step 7
     CreateModuleEnvironment(module);
-    let env = module.environment;
+    let env = GetModuleEnvironment(module);
 
     // Step 8
     let requestedModules = module.requestedModules;
     for (let i = 0; i < requestedModules.length; i++) {
         let required = requestedModules[i];
         let requiredModule = HostResolveImportedModule(module, required);
         requiredModule.declarationInstantiation();
     }
@@ -126,17 +126,17 @@ function ModuleDeclarationInstantiation(
     // Step 12
     let importEntries = module.importEntries;
     for (let i = 0; i < importEntries.length; i++) {
         let imp = importEntries[i];
         let importedModule = HostResolveImportedModule(module, imp.moduleRequest);
         if (imp.importName === "*") {
             // TODO
             // let namespace = GetModuleNamespace(importedModule);
-            // CreateNamespaceBinding(module.environment, imp.localName, namespace);
+            // CreateNamespaceBinding(env, imp.localName, namespace);
         } else {
             let resolution = importedModule.resolveExport(imp.importName);
             if (resolution === null)
                 ThrowSyntaxError(JSMSG_MISSING_IMPORT);
             if (resolution === "ambiguous")
                 ThrowSyntaxError(JSMSG_AMBIGUOUS_IMPORT);
             CreateImportBinding(env, imp.localName, resolution.module, resolution.bindingName);
         }
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -405,31 +405,27 @@ ModuleObject::setEvaluated()
 
 bool
 ModuleObject::evaluate(JSContext* cx, MutableHandleValue rval)
 {
     RootedScript script(cx, this->script());
     return JS_ExecuteScript(cx, script, rval);
 }
 
-DEFINE_GETTER_FUNCTIONS(ModuleObject, initialEnvironment, InitialEnvironmentSlot)
-DEFINE_GETTER_FUNCTIONS(ModuleObject, environment, EnvironmentSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, evaluated, EvaluatedSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, requestedModules, RequestedModulesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, importEntries, ImportEntriesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, localExportEntries, LocalExportEntriesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, indirectExportEntries, IndirectExportEntriesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, starExportEntries, StarExportEntriesSlot)
 
 JSObject*
 js::InitModuleClass(JSContext* cx, HandleObject obj)
 {
     static const JSPropertySpec protoAccessors[] = {
-        JS_PSG("initialEnvironment", ModuleObject_initialEnvironmentGetter, 0),
-        JS_PSG("environment", ModuleObject_environmentGetter, 0),
         JS_PSG("evaluated", ModuleObject_evaluatedGetter, 0),
         JS_PSG("requestedModules", ModuleObject_requestedModulesGetter, 0),
         JS_PSG("importEntries", ModuleObject_importEntriesGetter, 0),
         JS_PSG("localExportEntries", ModuleObject_localExportEntriesGetter, 0),
         JS_PSG("indirectExportEntries", ModuleObject_indirectExportEntriesGetter, 0),
         JS_PSG("starExportEntries", ModuleObject_starExportEntriesGetter, 0),
         JS_PS_END
     };
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -2881,16 +2881,90 @@ SetRNGState(JSContext* cx, unsigned argc
     if (!ToNumber(cx, args[0], &seed))
         return false;
 
     cx->compartment()->rngState = static_cast<uint64_t>(seed) & RNG_MASK;
     return true;
 }
 #endif
 
+static ModuleEnvironmentObject*
+GetModuleEnvironment(JSContext* cx, HandleValue moduleValue)
+{
+    RootedModuleObject module(cx, &moduleValue.toObject().as<ModuleObject>());
+
+    // Use the initial environment so that tests can check bindings exists
+    // before they have been instantiated.
+    RootedModuleEnvironmentObject env(cx, &module->initialEnvironment());
+    MOZ_ASSERT(env);
+    MOZ_ASSERT_IF(module->environment(), module->environment() == env);
+
+    return env;
+}
+
+static bool
+GetModuleEnvironmentNames(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (args.length() != 1) {
+        JS_ReportError(cx, "Wrong number of arguments");
+        return false;
+    }
+
+    if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
+        JS_ReportError(cx, "First argument should be a ModuleObject");
+        return false;
+    }
+
+    RootedModuleEnvironmentObject env(cx, GetModuleEnvironment(cx, args[0]));
+    Rooted<IdVector> ids(cx, IdVector(cx));
+    if (!JS_Enumerate(cx, env, &ids))
+        return false;
+
+    uint32_t length = ids.length();
+    RootedArrayObject array(cx, NewDenseFullyAllocatedArray(cx, length));
+    if (!array)
+        return false;
+
+    array->setDenseInitializedLength(length);
+    for (uint32_t i = 0; i < length; i++)
+        array->initDenseElement(i, StringValue(JSID_TO_STRING(ids[i])));
+
+    args.rval().setObject(*array);
+    return true;
+}
+
+static bool
+GetModuleEnvironmentValue(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (args.length() != 2) {
+        JS_ReportError(cx, "Wrong number of arguments");
+        return false;
+    }
+
+    if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
+        JS_ReportError(cx, "First argument should be a ModuleObject");
+        return false;
+    }
+
+    if (!args[1].isString()) {
+        JS_ReportError(cx, "Second argument should be a string");
+        return false;
+    }
+
+    RootedModuleEnvironmentObject env(cx, GetModuleEnvironment(cx, args[0]));
+    RootedString name(cx, args[1].toString());
+    RootedId id(cx);
+    if (!JS_StringToId(cx, name, &id))
+        return false;
+
+    return GetProperty(cx, env, env, id, args.rval());
+}
+
 static const JSFunctionSpecWithHelp TestingFunctions[] = {
     JS_FN_HELP("gc", ::GC, 0, 0,
 "gc([obj] | 'compartment' [, 'shrinking'])",
 "  Run the garbage collector. When obj is given, GC only its compartment.\n"
 "  If 'compartment' is given, GC any compartments that were scheduled for\n"
 "  GC via schedulegc.\n"
 "  If 'shrinking' is passed as the optional second argument, perform a\n"
 "  shrinking GC rather than a normal GC."),
@@ -3354,16 +3428,24 @@ gc::ZealModeHelpText),
 "  Enables the deprecated, non-standard __noSuchMethod__ feature.\n"),
 
 #ifdef DEBUG
     JS_FN_HELP("setRNGState", SetRNGState, 1, 0,
 "setRNGState(seed)",
 "  Set this compartment's RNG state.\n"),
 #endif
 
+    JS_FN_HELP("getModuleEnvironmentNames", GetModuleEnvironmentNames, 1, 0,
+"getModuleEnvironmentNames(module)",
+"  Get the list of a module environment's bound names for a specified module.\n"),
+
+    JS_FN_HELP("getModuleEnvironmentValue", GetModuleEnvironmentValue, 2, 0,
+"getModuleEnvironmentValue(module, name)",
+"  Get the value of a bound name in a module environment.\n"),
+
     JS_FS_HELP_END
 };
 
 static const JSPropertySpec TestingProperties[] = {
     JS_PSG("timesAccessed", TimesAccessed, 0),
     JS_PS_END
 };
 
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -947,17 +947,17 @@ Statistics::beginSlice(const ZoneGCStats
     this->zoneStats = zoneStats;
 
     bool first = !runtime->gc.isIncrementalGCInProgress();
     if (first)
         beginGC(gckind);
 
     SliceData data(budget, reason, PRMJ_Now(), JS_GetCurrentEmbedderTime(), GetPageFaultCount());
     if (!slices.append(data)) {
-        // OOM testing fails if we CrashAtUnhandlableOOM here.
+        // OOM testing fails if we crash here, so set a flag instead.
         aborted = true;
         return;
     }
 
     runtime->addTelemetry(JS_TELEMETRY_GC_REASON, reason);
 
     // Slice callbacks should only fire for the outermost level
     if (++gcDepth == 1) {
--- a/js/src/jit-test/tests/baseline/bug847425.js
+++ b/js/src/jit-test/tests/baseline/bug847425.js
@@ -1,9 +1,9 @@
-// |jit-test| allow-oom
+// |jit-test| allow-oom; allow-unhandlable-oom
 gcparam("maxBytes", gcparam("gcBytes") + 4*1024);
 var max = 400;
 function f(b) {
     if (b) {
         f(b - 1);
     } else {
         g = {
             apply:function(x,y) {            }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/bug-978802.js
@@ -0,0 +1,17 @@
+load(libdir + 'oomTest.js');
+
+oomTest(() => {
+    try {
+        var max = 400;
+        function f(b) {
+            if (b) {
+                f(b - 1);
+            } else {
+                g = {};
+            }
+            g.apply(null, arguments);
+        }
+        f(max - 1);
+    } catch(exc0) {}
+    f();
+});
--- a/js/src/jit-test/tests/modules/module-declaration-instantiation.js
+++ b/js/src/jit-test/tests/modules/module-declaration-instantiation.js
@@ -1,35 +1,30 @@
 // Exercise ModuleDeclarationInstantiation() operation.
 
-function parseAndInstantiate(source) {
-    let m = parseModule(source);
-    m.declarationInstantiation();
-    return m.environment;
-}
-
 function testModuleEnvironment(module, expected) {
-    var actual = Object.keys(module.environment);
+    var actual = getModuleEnvironmentNames(module);
     assertEq(actual.length, expected.length);
     for (var i = 0; i < actual.length; i++) {
         assertEq(actual[i], expected[i]);
     }
 }
 
 // Check the environment of an empty module.
-let e = parseAndInstantiate("");
-assertEq(Object.keys(e).length, 0);
+let m = parseModule("");
+m.declarationInstantiation();
+testModuleEnvironment(m, []);
 
 let moduleRepo = new Map();
 setModuleResolveHook(function(module, specifier) {
     if (specifier in moduleRepo)
         return moduleRepo[specifier];
     throw "Module " + specifier + " not found";
 });
 
-a = moduleRepo['a'] = parseModule("var x = 1; export { x };");
-b = moduleRepo['b'] = parseModule("import { x as y } from 'a';");
+let a = moduleRepo['a'] = parseModule("var x = 1; export { x };");
+let b = moduleRepo['b'] = parseModule("import { x as y } from 'a';");
 
 a.declarationInstantiation();
 b.declarationInstantiation();
 
 testModuleEnvironment(a, ['x']);
 testModuleEnvironment(b, ['y']);
--- a/js/src/jit-test/tests/modules/module-environment.js
+++ b/js/src/jit-test/tests/modules/module-environment.js
@@ -1,20 +1,18 @@
 load(libdir + "class.js");
 
 // Test top-level module environment
 
 function testInitialEnvironment(source, expected) {
-    print(source);
-    let m = parseModule(source);
-    let scope = m.initialEnvironment;
-    let keys = Object.keys(scope);
-    assertEq(keys.length, expected.length);
+    let module = parseModule(source);
+    let names = getModuleEnvironmentNames(module);
+    assertEq(names.length, expected.length);
     expected.forEach(function(name) {
-        assertEq(name in scope, true);
+        assertEq(names.includes(name), true);
     });
 }
 
 testInitialEnvironment('', []);
 testInitialEnvironment('var x = 1;', ['x']);
 testInitialEnvironment('let x = 1;', ['x']);
 testInitialEnvironment("if (true) { let x = 1; }", []);
 testInitialEnvironment("if (true) { var x = 1; }", ['x']);
--- a/js/src/jit-test/tests/modules/module-evaluation.js
+++ b/js/src/jit-test/tests/modules/module-evaluation.js
@@ -21,25 +21,25 @@ assertEq(typeof parseAndEvaluate(""), "u
 // Check evaluation returns evaluation result the first time, then undefined.
 let m = parseModule("1");
 m.declarationInstantiation();
 assertEq(m.evaluation(), 1);
 assertEq(typeof m.evaluation(), "undefined");
 
 // Check top level variables are initialized by evaluation.
 m = parseModule("export var x = 2 + 2;");
-assertEq(typeof m.initialEnvironment.x, "undefined");
+assertEq(typeof getModuleEnvironmentValue(m, "x"), "undefined");
 m.declarationInstantiation();
 m.evaluation();
-assertEq(m.environment.x, 4);
+assertEq(getModuleEnvironmentValue(m, "x"), 4);
 
 m = parseModule("export let x = 2 * 3;");
 m.declarationInstantiation();
 m.evaluation();
-assertEq(m.environment.x, 6);
+assertEq(getModuleEnvironmentValue(m, "x"), 6);
 
 // Set up a module to import from.
 let a = moduleRepo['a'] =
 parseModule(`var x = 1;
              export { x };
              export default 2;
              export function f(x) { return x + 1; }`);
 
@@ -94,9 +94,10 @@ assertDeepEq(parseAndEvaluate(`import { 
                                import { x as x2, y as y2 } from 'c2';
                                [x1, y1, x2, y2]`),
              [1, 2, 1, 2]);
 
 // Import access in functions
 m = parseModule("import { x } from 'a'; function f() { return x; }")
 m.declarationInstantiation();
 m.evaluation();
-assertEq(m.environment.f(), 1);
+let f = getModuleEnvironmentValue(m, "f");
+assertEq(f(), 1);
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -2026,17 +2026,17 @@ SnapshotIterator::floatAllocationPointer
 Value
 SnapshotIterator::maybeRead(const RValueAllocation& a, MaybeReadFallback& fallback)
 {
     if (allocationReadable(a))
         return allocationValue(a);
 
     if (fallback.canRecoverResults()) {
         if (!initInstructionResults(fallback))
-            js::CrashAtUnhandlableOOM("Unable to recover allocations.");
+            MOZ_CRASH("Unable to recover allocations.");
 
         if (allocationReadable(a))
             return allocationValue(a);
 
         MOZ_ASSERT_UNREACHABLE("All allocations should be readable.");
     }
 
     return fallback.unreadablePlaceholder();
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -988,17 +988,17 @@ class BOffImm
     uint32_t data;
 
   public:
     explicit BOffImm(int offset)
       : data ((offset - 8) >> 2 & 0x00ffffff)
     {
         MOZ_ASSERT((offset & 0x3) == 0);
         if (!IsInRange(offset))
-            CrashAtUnhandlableOOM("BOffImm");
+            MOZ_CRASH("BOffImm offset out of range");
     }
 
     explicit BOffImm()
       : data(INVALID)
     { }
 
   private:
     BOffImm(Instruction& inst);
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -1194,29 +1194,31 @@ JS::AutoCheckRequestDepth::~AutoCheckReq
         MOZ_ASSERT(cx->runtime()->checkRequestDepth != 0);
         cx->runtime()->checkRequestDepth--;
     }
 }
 
 #endif
 
 #ifdef JS_CRASH_DIAGNOSTICS
-void CompartmentChecker::check(InterpreterFrame* fp)
+void
+CompartmentChecker::check(InterpreterFrame* fp)
 {
     if (fp)
         check(fp->scopeChain());
 }
 
-void CompartmentChecker::check(AbstractFramePtr frame)
+void
+CompartmentChecker::check(AbstractFramePtr frame)
 {
     if (frame)
         check(frame.scopeChain());
 }
 #endif
 
 void
-js::CrashAtUnhandlableOOM(const char* reason)
+AutoEnterOOMUnsafeRegion::crash(const char* reason)
 {
     char msgbuf[1024];
     JS_snprintf(msgbuf, sizeof(msgbuf), "[unhandlable oom] %s", reason);
     MOZ_ReportAssertionFailure(msgbuf, __FILE__, __LINE__);
     MOZ_CRASH();
 }
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -3543,30 +3543,34 @@ js::CloneScriptIntoFunction(JSContext* c
     //
     // This is so that when cloning nested functions, they can walk the static
     // scope chain via fun and correctly compute the presence of a
     // non-syntactic global.
     RootedScript dst(cx, CreateEmptyScriptForClone(cx, enclosingScope, src));
     if (!dst)
         return nullptr;
 
+    // Save flags in case we need to undo the early mutations.
+    const int preservedFlags = fun->flags();
+
     dst->setFunction(fun);
     Rooted<LazyScript*> lazy(cx);
     if (fun->isInterpretedLazy()) {
         lazy = fun->lazyScriptOrNull();
         fun->setUnlazifiedScript(dst);
     } else {
         fun->initScript(dst);
     }
 
     if (!detail::CopyScript(cx, fun, src, dst)) {
         if (lazy)
             fun->initLazyScript(lazy);
         else
             fun->setScript(nullptr);
+        fun->setFlags(preservedFlags);
         return nullptr;
     }
 
     return dst;
 }
 
 DebugScript*
 JSScript::debugScript()
@@ -4018,27 +4022,21 @@ JSScript::argumentsOptimizationFailed(JS
          * implies fp->hasArgsObj", the Ion bail mechanism will create an
          * arguments object right after restoring the BaselineFrame and before
          * entering Baseline code (in jit::FinishBailoutToBaseline).
          */
         if (i.isIon())
             continue;
         AbstractFramePtr frame = i.abstractFramePtr();
         if (frame.isFunctionFrame() && frame.script() == script) {
+            /* We crash on OOM since cleaning up here would be complicated. */
+            AutoEnterOOMUnsafeRegion oomUnsafe;
             ArgumentsObject* argsobj = ArgumentsObject::createExpected(cx, frame);
-            if (!argsobj) {
-                /*
-                 * We can't leave stack frames with script->needsArgsObj but no
-                 * arguments object. It is, however, safe to leave frames with
-                 * an arguments object but !script->needsArgsObj.
-                 */
-                script->needsArgsObj_ = false;
-                return false;
-            }
-
+            if (!argsobj)
+                oomUnsafe.crash("JSScript::argumentsOptimizationFailed");
             SetFrameArgumentsObject(cx, frame, script, argsobj);
         }
     }
 
     return true;
 }
 
 bool
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1289,16 +1289,33 @@ intrinsic_HostResolveImportedModule(JSCo
         return false;
     }
 
     args.rval().set(result);
     return true;
 }
 
 static bool
+intrinsic_GetModuleEnvironment(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 1);
+    RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
+    RootedModuleEnvironmentObject env(cx, module->environment());
+    args.rval().setUndefined();
+    if (!env) {
+        args.rval().setUndefined();
+        return true;
+    }
+
+    args.rval().setObject(*env);
+    return true;
+}
+
+static bool
 intrinsic_CreateModuleEnvironment(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
     RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
     module->createEnvironment();
     args.rval().setUndefined();
     return true;
@@ -1581,17 +1598,18 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("regexp_exec_no_statics", regexp_exec_no_statics, 2,0),
     JS_FN("regexp_test_no_statics", regexp_test_no_statics, 2,0),
     JS_FN("regexp_construct_no_statics", regexp_construct_no_statics, 2,0),
 
     JS_FN("IsModule", intrinsic_IsModule, 1, 0),
     JS_FN("CallModuleMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<ModuleObject>>, 2, 0),
     JS_FN("HostResolveImportedModule", intrinsic_HostResolveImportedModule, 2, 0),
-    JS_FN("CreateModuleEnvironment", intrinsic_CreateModuleEnvironment, 2, 0),
+    JS_FN("GetModuleEnvironment", intrinsic_GetModuleEnvironment, 1, 0),
+    JS_FN("CreateModuleEnvironment", intrinsic_CreateModuleEnvironment, 1, 0),
     JS_FN("CreateImportBinding", intrinsic_CreateImportBinding, 4, 0),
     JS_FN("SetModuleEvaluated", intrinsic_SetModuleEvaluated, 1, 0),
     JS_FN("EvaluateModule", intrinsic_EvaluateModule, 1, 0),
 
     JS_FS_END
 };
 
 void
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -729,19 +729,23 @@ SCOutput::extractBuffer(uint64_t** datap
 
 JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
 
 JSStructuredCloneWriter::~JSStructuredCloneWriter()
 {
     // Free any transferable data left lying around in the buffer
     uint64_t* data;
     size_t size;
-    MOZ_ALWAYS_TRUE(extractBuffer(&data, &size));
-    DiscardTransferables(data, size, callbacks, closure);
-    js_free(data);
+    {
+        AutoEnterOOMUnsafeRegion oomUnsafe;
+        if (!extractBuffer(&data, &size))
+            oomUnsafe.crash("Unable to extract clone buffer");
+        DiscardTransferables(data, size, callbacks, closure);
+        js_free(data);
+    }
 }
 
 bool
 JSStructuredCloneWriter::parseTransferable()
 {
     MOZ_ASSERT(transferableObjects.empty(), "parseTransferable called with stale data");
 
     if (transferable.isNull() || transferable.isUndefined())
--- a/layout/reftests/svg/filters/feDiffuseLighting-1-ref.svg
+++ b/layout/reftests/svg/filters/feDiffuseLighting-1-ref.svg
@@ -1,7 +1,16 @@
 <!--
      Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>
-  <image width='100' height='100' xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAG6SURBVHhe7d3BbcJAGAXhJYXQg32mCV8p2vRAEb4mREoJe/g2GiTf55/hiRu+jTG+P08fxMAXwhHGn4GCYF+FghQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDGfqQh6Px9j3HTtxLZypQZ7P5ziOYy0DGO3UIPf7fWzbhp24Fs7UIGudbtIWBOtSkIJgBjCcFlIQzACG00IKghnAcFpIQTADGE4LKQhmAMNpIQXBDGA4LaQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgtpCCYAQynhRQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCcFlIQzACG00IKghnAcFrIfw7yfr/H6/XCTlwL5/bBnfY+9d8/47+ua5znuZYFiHZqEOiuZVH6DcHSFaQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgtBAvyA+wPEw79WuphAAAAAElFTkSuQmCC"/>
+  <image width='100' height='100'
+         xlink:href="data:image/png;base64,
+iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI
+WXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3wkbDDIilL5aRAAAAB1pVFh0Q29tbWVudAAAAAAAQ3Jl
+YXRlZCB3aXRoIEdJTVBkLmUHAAAA9UlEQVR42u3csRHDIBBFwbPHddADiqmEduhFlSiWelBOSirX
+YFsB49lXAMHf4DIeEXGFpqi1Fk8zzFPOGchsAQEiIEAEBIiAABEQIAIiIEAEBIiAAPmgUkosy2LV
+H3rd+VitNXrvse+7ZWcASSlFSsmqbggQAQEiIAICRECACAgQAQEiIEAERECACAgQAQEiIEAERECA
+CAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAfnHbv3Z+jzP6L1bdRaQdV1j
+jGHVWUC2bbOoGwJEQIAIiIAAERAgAgJEQIAIiIAAERAg+qrjOOINBcEbJFN4kugAAAAASUVORK5C
+YII="/>
 </svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/filters/filter-lighting-region-ref.svg
@@ -0,0 +1,11 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg" width="600px" height="300px">
+
+  <rect x="50" y="50" width="200" height="200" fill="black" />
+
+  <rect x="340" y="40" width="220" height="220" fill="grey" />
+  <rect x="350" y="50" width="200" height="200" fill="white" />
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/filters/filter-lighting-region.svg
@@ -0,0 +1,30 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg" width="600px" height="300px">
+  <!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=1203376 -->
+  <defs>
+    <!-- the filter lights are intentionally chosen to fill the entire area with
+         a solid color since we're only testing the extents of the filter region
+    -->
+    <filter id="diffuse" x="-50%" y="-50%" width="200%" height="200%">
+      <!-- gives a black filter region -->
+      <feDiffuseLighting lighting-color="black">
+        <feDistantLight />
+      </feDiffuseLighting>
+    </filter>
+
+    <filter id="specular" x="-50%" y="-50%" width="200%" height="200%">
+      <!-- gives a white filter region -->
+      <feSpecularLighting lighting-color="white" specularConstant="100">
+        <feDistantLight elevation="90"/>
+      </feSpecularLighting>
+    </filter>
+  </defs>
+
+  <rect x="100" y="100" width="100" height="100" filter="url(#diffuse)" />
+
+  <rect x="340" y="40" width="220" height="220" fill="grey" />
+  <rect x="400" y="100" width="100" height="100" filter="url(#specular)" />
+</svg>
--- a/layout/reftests/svg/filters/reftest.list
+++ b/layout/reftests/svg/filters/reftest.list
@@ -104,19 +104,21 @@ fuzzy(2,500) == feDisplacementMap-colour
 == feMorphology-radius-negative-02.svg pass.svg
 == feMorphology-radius-zero-01.svg pass.svg
 == feMorphology-radius-zero-02.svg pass.svg
 
 == feTile-large-01.svg pass.svg
 == feTile-large-02.svg feTile-large-02-ref.svg
 == feTile-outside-01.svg feTile-outside-01-ref.svg
 
-fuzzy(1,119) == feDiffuseLighting-1.svg feDiffuseLighting-1-ref.svg
+fuzzy(1,217) == feDiffuseLighting-1.svg feDiffuseLighting-1-ref.svg
 
 fuzzy(2,2659) skip-if(d2d) == feSpecularLighting-1.svg feSpecularLighting-1-ref.svg
 
+== filter-lighting-region.svg filter-lighting-region-ref.svg
+
 == fePointLight-zoomed-page.svg fePointLight-zoomed-page-ref.svg
 
 == feTurbulence-offset.svg feTurbulence-offset-ref.svg
 
 == outside-sourcegraphic-1.svg outside-sourcegraphic-ref.svg
 == outside-sourcegraphic-2.svg outside-sourcegraphic-ref.svg
 == outside-sourcegraphic-3.svg outside-sourcegraphic-ref.svg
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
@@ -279,18 +279,16 @@ static void nr_ice_candidate_mark_done(n
       assert(0);
       return;
     }
 
     /* If this is a relay candidate, there's likely to be a srflx that is
      * piggybacking on it. Make sure it is marked done too. */
     if ((cand->type == RELAYED) && cand->u.relayed.srvflx_candidate) {
       nr_ice_candidate *srflx=cand->u.relayed.srvflx_candidate;
-      /* Calling done_cb can destroy this, make sure it doesn't dangle. */
-      cand->u.relayed.srvflx_candidate=0;
       if (state == NR_ICE_CAND_STATE_INITIALIZED &&
           nr_turn_client_get_mapped_address(cand->u.relayed.turn,
                                             &srflx->addr)) {
         r_log(LOG_ICE, LOG_WARNING, "ICE(%s)/CAND(%s): Failed to get mapped address from TURN allocate response, srflx failed.", cand->ctx->label, cand->label);
         nr_ice_candidate_mark_done(srflx, NR_ICE_CAND_STATE_FAILED);
       } else {
         nr_ice_candidate_mark_done(srflx, state);
       }
@@ -320,23 +318,27 @@ int nr_ice_candidate_destroy(nr_ice_cand
 
     switch(cand->type){
       case HOST:
         break;
 #ifdef USE_TURN
       case RELAYED:
         if (cand->u.relayed.turn_handle)
           nr_ice_socket_deregister(cand->isock, cand->u.relayed.turn_handle);
+        if (cand->u.relayed.srvflx_candidate)
+          cand->u.relayed.srvflx_candidate->u.srvrflx.relay_candidate=0;
         nr_turn_client_ctx_destroy(&cand->u.relayed.turn);
         nr_socket_destroy(&cand->u.relayed.turn_sock);
         break;
 #endif /* USE_TURN */
       case SERVER_REFLEXIVE:
         if (cand->u.srvrflx.stun_handle)
           nr_ice_socket_deregister(cand->isock, cand->u.srvrflx.stun_handle);
+        if (cand->u.srvrflx.relay_candidate)
+          cand->u.srvrflx.relay_candidate->u.relayed.srvflx_candidate=0;
         nr_stun_client_ctx_destroy(&cand->u.srvrflx.stun);
         break;
       default:
         break;
     }
 
     NR_async_timer_cancel(cand->delay_timer);
     NR_async_timer_cancel(cand->ready_cb_timer);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.h
@@ -71,16 +71,19 @@ struct nr_ice_candidate_ {
   void *delay_timer;
   void *resolver_handle;
 
   /* Holding data for STUN and TURN */
   union {
     struct {
       nr_stun_client_ctx *stun;
       void *stun_handle;
+      /* If this is a srflx that is piggybacking on a relay candidate, this is
+       * a back pointer to that relay candidate. */
+      nr_ice_candidate *relay_candidate;
     } srvrflx;
     struct {
       nr_turn_client_ctx *turn;
       nr_ice_turn_server *server;
       nr_ice_candidate *srvflx_candidate;
       nr_socket *turn_sock;
       void *turn_handle;
     } relayed;
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
@@ -281,17 +281,20 @@ static int nr_ice_component_initialize_u
         }
         /* relayed*/
         if(r=nr_socket_turn_create(sock, &turn_sock))
           ABORT(r);
         if(r=nr_ice_candidate_create(ctx,component,
           isock,turn_sock,RELAYED,0,
           &ctx->turn_servers[j].turn_server,component->component_id,&cand))
            ABORT(r);
-        cand->u.relayed.srvflx_candidate=srvflx_cand;
+        if (srvflx_cand) {
+          cand->u.relayed.srvflx_candidate=srvflx_cand;
+          srvflx_cand->u.srvrflx.relay_candidate=cand;
+        }
         cand->u.relayed.server=&ctx->turn_servers[j];
         TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp);
         component->candidate_ct++;
 
         cand=0;
       }
 #endif /* USE_TURN */
 
--- a/mfbt/Assertions.h
+++ b/mfbt/Assertions.h
@@ -16,16 +16,30 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/Compiler.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MacroArgs.h"
 #ifdef MOZ_DUMP_ASSERTION_STACK
 #include "nsTraceRefcnt.h"
 #endif
 
+#if defined(MOZ_CRASHREPORTER) && defined(MOZILLA_INTERNAL_API) && \
+    !defined(MOZILLA_EXTERNAL_LINKAGE) && defined(__cplusplus)
+#  define MOZ_CRASH_CRASHREPORT
+namespace CrashReporter {
+// This declaration is present here as well as in nsExceptionHandler.h
+// nsExceptionHandler.h is not directly included in this file as it includes
+// windows.h, which can cause problems when it is imported into some files due
+// to the number of macros defined.
+// XXX If you change this definition - also change the definition in
+// nsExceptionHandler.h
+void AnnotateMozCrashReason(const char* aReason);
+} // namespace CrashReporter
+#endif
+
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 #ifdef WIN32
    /*
     * TerminateProcess and GetCurrentProcess are defined in <winbase.h>, which
     * further depends on <windef.h>.  We hardcode these few definitions manually
     * because those headers clutter the global namespace with a significant
@@ -244,17 +258,25 @@ MOZ_ReportCrash(const char* aStr, const 
  *
  * If we're a DEBUG build and we crash at a MOZ_CRASH which provides an
  * explanation-string, we print the string to stderr.  Otherwise, we don't
  * print anything; this is because we want MOZ_CRASH to be 100% safe in release
  * builds, and it's hard to print to stderr safely when memory might have been
  * corrupted.
  */
 #ifndef DEBUG
-#  define MOZ_CRASH(...) MOZ_REALLY_CRASH()
+#  ifdef MOZ_CRASH_CRASHREPORT
+#    define MOZ_CRASH(...) \
+       do { \
+         CrashReporter::AnnotateMozCrashReason("MOZ_CRASH(" __VA_ARGS__ ")"); \
+         MOZ_REALLY_CRASH(); \
+       } while (0)
+#  else
+#    define MOZ_CRASH(...) MOZ_REALLY_CRASH()
+#  endif
 #else
 #  define MOZ_CRASH(...) \
      do { \
        MOZ_ReportCrash("" __VA_ARGS__, __FILE__, __LINE__); \
        MOZ_REALLY_CRASH(); \
      } while (0)
 #endif
 
@@ -504,10 +526,11 @@ struct AssertionConditionType
   do { \
     if ( ( expr ) ) {                       \
       /* Silence MOZ_WARN_UNUSED_RESULT. */ \
     } \
   } while (0)
 #endif
 
 #undef MOZ_DUMP_ASSERTION_STACK
+#undef MOZ_CRASH_CRASHREPORT
 
 #endif /* mozilla_Assertions_h */
--- a/netwerk/base/nsIPackagedAppUtils.idl
+++ b/netwerk/base/nsIPackagedAppUtils.idl
@@ -19,17 +19,17 @@ interface nsIVerificationCallback;
  * store the hash values of each resource. When a resource is ready, Necko
  * will calculate its hash value (including the header like Content-Location: xxx),
  * and calls checkIntegrity(...) to verify the integrity.
  *
  * For more detail:
  *   https://wiki.mozilla.org/FirefoxOS/New_security_model/Packaging
  */
 
-[scriptable, uuid(d0a98a69-a215-4cf9-abb3-7a0b9237cd27)]
+[scriptable, uuid(cc245638-6a38-4f70-8d77-21c55aabd636)]
 interface nsIPackagedAppUtils : nsISupports
 {
   /**
    * @aHeader is the package's header including
    *   - "manifest-signature: xxxxxx" (base64 encoding)
    * @aManifest is the manifest of the package
    *   - the multipart header is included
    *   - manifest must be the first resource of the package
@@ -43,16 +43,22 @@ interface nsIPackagedAppUtils : nsISuppo
    * @aFileName is the name of a resource in the package
    * @aHashValue is the hash value of this resource named aFileName
    *   - aHashValue should be computed by the caller of this method
    * @aCallback is the callback, see comments of nsIVerificationCallback below
    */
   void checkIntegrity(in ACString aFileName,
                       in ACString aHashValue,
                       in nsIVerificationCallback aVerifier);
+
+  /**
+   * The package identifier for signed package. Only available after the
+   * manifest is verified.
+   */
+  readonly attribute ACString packageIdentifier;
 };
 
 /**
   * The callback passed to verifyManifest and checkIntegrity
   */
 [scriptable, uuid(e1912028-93e5-4378-aa3f-a58702937169)]
 interface nsIVerificationCallback : nsISupports
 {
--- a/netwerk/base/nsStandardURL.cpp
+++ b/netwerk/base/nsStandardURL.cpp
@@ -417,39 +417,42 @@ nsStandardURL::NormalizeIDN(const nsCSub
         return true;
     }
 
     result.Truncate();
     return false;
 }
 
 bool
-nsStandardURL::ValidIPv6orHostname(const char *host)
+nsStandardURL::ValidIPv6orHostname(const char *host, uint32_t length)
 {
-    if (!host || !*host) {
-        // Should not be NULL or empty string
+    if (!host) {
         return false;
     }
 
-    int32_t length = strlen(host);
+    if (length != strlen(host)) {
+        // Embedded null
+        return false;
+    }
 
     bool openBracket = host[0] == '[';
     bool closeBracket = host[length - 1] == ']';
 
     if (openBracket && closeBracket) {
         return net_IsValidIPv6Addr(host + 1, length - 2);
     }
 
     if (openBracket || closeBracket) {
         // Fail if only one of the brackets is present
         return false;
     }
 
-    if (PL_strchr(host, ':')) {
-        // Hostnames should not contain a colon
+    const char *end = host + length;
+    if (end != net_FindCharInSet(host, end, "\t\n\v\f\r #/:?@[\\]")) {
+        // % is allowed because we don't do hostname percent decoding yet.
         return false;
     }
 
     return true;
 }
 
 void
 nsStandardURL::CoalescePath(netCoalesceFlags coalesceFlag, char *path)
@@ -577,16 +580,21 @@ nsStandardURL::BuildNormalizedSpec(const
         if (tempHost.Contains('\0'))
             return NS_ERROR_MALFORMED_URI;  // null embedded in hostname
         if (tempHost.Contains(' '))
             return NS_ERROR_MALFORMED_URI;  // don't allow spaces in the hostname
         if ((useEncHost = NormalizeIDN(tempHost, encHost)))
             approxLen += encHost.Length();
         else
             approxLen += mHost.mLen;
+
+        if ((useEncHost && !ValidIPv6orHostname(encHost.BeginReading(), encHost.Length())) ||
+            (!useEncHost && !ValidIPv6orHostname(tempHost.BeginReading(), tempHost.Length()))) {
+            return NS_ERROR_MALFORMED_URI;
+        }
     }
 
     //
     // generate the normalized URL string
     //
     // approxLen should be correct or 1 high
     if (!mSpec.SetLength(approxLen+1, mozilla::fallible)) // buf needs a trailing '\0' below
         return NS_ERROR_OUT_OF_MEMORY;
@@ -1604,32 +1612,32 @@ nsStandardURL::SetHost(const nsACString 
     if (strlen(host) < flat.Length())
         return NS_ERROR_MALFORMED_URI; // found embedded null
 
     // For consistency with SetSpec/nsURLParsers, don't allow spaces
     // in the hostname.
     if (strchr(host, ' '))
         return NS_ERROR_MALFORMED_URI;
 
-    if (!ValidIPv6orHostname(host)) {
-        return NS_ERROR_MALFORMED_URI;
-    }
-
     InvalidateCache();
     mHostEncoding = eEncoding_ASCII;
 
-    int32_t len;
+    uint32_t len;
     nsAutoCString hostBuf;
     if (NormalizeIDN(flat, hostBuf)) {
         host = hostBuf.get();
         len = hostBuf.Length();
     }
     else
         len = flat.Length();
 
+    if (!ValidIPv6orHostname(host, len)) {
+        return NS_ERROR_MALFORMED_URI;
+    }
+
     if (mHost.mLen < 0) {
         int port_length = 0;
         if (mPort != -1) {
             nsAutoCString buf;
             buf.Assign(':');
             buf.AppendInt(mPort);
             port_length = buf.Length();
         }
--- a/netwerk/base/nsStandardURL.h
+++ b/netwerk/base/nsStandardURL.h
@@ -171,17 +171,17 @@ protected:
     virtual nsresult EnsureFile();
 
 private:
     int32_t  Port() { return mPort == -1 ? mDefaultPort : mPort; }
 
     void     Clear();
     void     InvalidateCache(bool invalidateCachedFile = true);
 
-    bool     ValidIPv6orHostname(const char *host);
+    bool     ValidIPv6orHostname(const char *host, uint32_t aLen);
     bool     NormalizeIDN(const nsCSubstring &host, nsCString &result);
     void     CoalescePath(netCoalesceFlags coalesceFlag, char *path);
 
     uint32_t AppendSegmentToBuf(char *, uint32_t, const char *, URLSegment &, const nsCString *esc=nullptr, bool useEsc = false);
     uint32_t AppendToBuf(char *, uint32_t, const char *, uint32_t);
 
     nsresult BuildNormalizedSpec(const char *spec);
 
--- a/netwerk/protocol/http/PackagedAppUtils.js
+++ b/netwerk/protocol/http/PackagedAppUtils.js
@@ -8,17 +8,17 @@
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const PACKAGEDAPPUTILS_CONTRACTID = "@mozilla.org/network/packaged-app-utils;1";
 const PACKAGEDAPPUTILS_CID = Components.ID("{fe8f1c2e-3c13-11e5-9a3f-bbf47d1e6697}");
 
 function PackagedAppUtils() {
-
+  this.packageIdentifier = '';
 }
 
 var DEBUG = 0
 function debug(s) {
   if (DEBUG) {
     dump("-*- PackagedAppUtils: " + s + "\n");
   }
 }
@@ -52,17 +52,19 @@ PackagedAppUtils.prototype = {
       // Base64 decode
       signature = atob(signature);
 
       // Remove header
       let manifestBody = aManifest.substr(aManifest.indexOf('\r\n\r\n') + 4);
       debug("manifestBody: " + manifestBody);
 
       // Parse manifest, store resource hashes
-      this.resources = JSON.parse(manifestBody)["moz-resources"];
+      let manifestObj = JSON.parse(manifestBody);
+      this.resources = manifestObj["moz-resources"];
+      this.packageIdentifier = manifestObj["package-identifier"];
     } catch (e) {
       debug("JSON parsing failure");
       aCallback.fireVerifiedEvent(true, false);
       return;
     }
 
     let manifestStream = Cc["@mozilla.org/io/string-input-stream;1"]
                            .createInstance(Ci.nsIStringInputStream);
--- a/netwerk/streamconv/converters/nsMultiMixedConv.cpp
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
@@ -659,16 +659,20 @@ nsMultiMixedConv::OnDataAvailable(nsIReq
                 } else {
                     // Token is probed.
                     mToken = Substring(tokenPos, mTokenLen);
                     mPreamble = nsCString(Substring(buffer, tokenPos));
 
                     // Push the cursor to the token so that the while loop below will
                     // find token from the right position.
                     cursor = tokenPos;
+
+                    // Update bufLen to exlude the preamble. Otherwise, the first
+                    // |SendData| would claim longer buffer length.
+                    bufLen -= mPreamble.Length();
                 }
             } else {
                 // If the boundary was set in the header,
                 // we need to check it matches with the one in the file.
                 if (mTokenLen &&
                     !StringBeginsWith(Substring(firstBuffer, 2), mToken)) {
                     return NS_ERROR_FAILURE;
                 }
--- a/netwerk/test/unit/test_multipart_streamconv_application_package.js
+++ b/netwerk/test/unit/test_multipart_streamconv_application_package.js
@@ -71,21 +71,27 @@ function contentHandler_chunked_headers(
 
 function contentHandler_type_missing(metadata, response)
 {
   response.setHeader("Content-Type", 'text/plain');
   var body = testData.getData();
   response.bodyOutputStream.write(body, body.length);
 }
 
-function contentHandler_with_package_header(metadata, response)
+function contentHandler_with_package_header(chunkSize, metadata, response)
 {
   response.setHeader("Content-Type", 'application/package');
   var body = testData.packageHeader + testData.getData();
-  response.bodyOutputStream.write(body, body.length);
+
+  response.bodyOutputStream.write(body.substring(0,chunkSize), chunkSize);
+  response.processAsync();
+  do_timeout(5, function() {
+    response.bodyOutputStream.write(body.substring(chunkSize), body.length-chunkSize);
+    response.finish();
+  });
 }
 
 var testData = {
   packageHeader: 'manifest-signature: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk\r\n',
   content: [
    { headers: ["Content-Location: /index.html", "Content-Type: text/html"], data: "<html>\r\n  <head>\r\n    <script src=\"/scripts/app.js\"></script>\r\n    ...\r\n  </head>\r\n  ...\r\n</html>\r\n", type: "text/html" },
    { headers: ["Content-Location: /scripts/app.js", "Content-Type: text/javascript"], data: "module Math from '/scripts/helpers/math.js';\r\n...\r\n", type: "text/javascript" },
    { headers: ["Content-Location: /scripts/helpers/math.js", "Content-Type: text/javascript"], data: "export function sum(nums) { ... }\r\n...\r\n", type: "text/javascript" }
@@ -239,39 +245,69 @@ function test_multipart_content_type_oth
            "*/*",
            new multipartListener(testData, true),
            null);
 
   var chan = make_channel(uri + "/multipart4");
   chan.asyncOpen(conv, null);
 }
 
-function test_multipart_package_header() {
+function test_multipart_package_header(aChunkSize) {
   var streamConv = Cc["@mozilla.org/streamConverters;1"]
                      .getService(Ci.nsIStreamConverterService);
 
   var conv = streamConv.asyncConvertData("application/package",
            "*/*",
            new multipartListener(testData, false, true),
            null);
 
-  var chan = make_channel(uri + "/multipart5");
+  var chan = make_channel(uri + "/multipart5_" + aChunkSize);
   chan.asyncOpen(conv, null);
 }
 
+// Bug 1212223 - Test multipart with package header and different chunk size.
+// Use explict function name to make the test case log more readable.
+
+function test_multipart_package_header_50() {
+  return test_multipart_package_header(50);
+}
+
+function test_multipart_package_header_100() {
+  return test_multipart_package_header(100);
+}
+
+function test_multipart_package_header_150() {
+  return test_multipart_package_header(150);
+}
+
+function test_multipart_package_header_200() {
+  return test_multipart_package_header(200);
+}
+
 function run_test()
 {
   httpserver = new HttpServer();
   httpserver.registerPathHandler("/multipart", contentHandler);
   httpserver.registerPathHandler("/multipart2", contentHandler_with_boundary);
   httpserver.registerPathHandler("/multipart3", contentHandler_chunked_headers);
   httpserver.registerPathHandler("/multipart4", contentHandler_type_missing);
-  httpserver.registerPathHandler("/multipart5", contentHandler_with_package_header);
+
+  // Bug 1212223 - Test multipart with package header and different chunk size.
+  httpserver.registerPathHandler("/multipart5_50", contentHandler_with_package_header.bind(null, 50));
+  httpserver.registerPathHandler("/multipart5_100", contentHandler_with_package_header.bind(null, 100));
+  httpserver.registerPathHandler("/multipart5_150", contentHandler_with_package_header.bind(null, 150));
+  httpserver.registerPathHandler("/multipart5_200", contentHandler_with_package_header.bind(null, 200));
+
   httpserver.start(-1);
 
   run_next_test();
 }
 
 add_test(test_multipart);
 add_test(test_multipart_with_boundary);
 add_test(test_multipart_chunked_headers);
 add_test(test_multipart_content_type_other);
-add_test(test_multipart_package_header);
+
+// Bug 1212223 - Test multipart with package header and different chunk size.
+add_test(test_multipart_package_header_50);
+add_test(test_multipart_package_header_100);
+add_test(test_multipart_package_header_150);
+add_test(test_multipart_package_header_200);
--- a/netwerk/test/unit/test_packaged_app_utils.js
+++ b/netwerk/test/unit/test_packaged_app_utils.js
@@ -1,24 +1,24 @@
 const header_missing_signature = "header1: content1";
 const header_invalid_signature = "header1: content1\r\nmanifest-signature: invalid-signature\r\n";
-const header = "manifest-signature: MIIF1AYJKoZIhvcNAQcCoIIFxTCCBcECAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCA54wggOaMIICgqADAgECAgECMA0GCSqGSIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEkMCIGA1UEChMbRXhhbXBsZSBUcnVzdGVkIENvcnBvcmF0aW9uMRkwFwYDVQQDExBUcnVzdGVkIFZhbGlkIENBMB4XDTE1MDkxMDA4MDQzNVoXDTM1MDkxMDA4MDQzNVowdDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGjAYBgNVBAMTEVRydXN0ZWQgQ29ycCBDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAts8whjOzEbn/w1xkFJ67af7F/JPujBK91oyJekh2schIMzFau9pY8S1AiJQoJCulOJCJfUc8hBLKBZiGAkii+4Gpx6cVqMLe6C22MdD806Soxn8Dg4dQqbIvPuI4eeVKu5CEk80PW/BaFMmRvRHO62C7PILuH6yZeGHC4P7dTKpsk4CLxh/jRGXLC8jV2BCW0X+3BMbHBg53NoI9s1Gs7KGYnfOHbBP5wEFAa00RjHnubUaCdEBlC8Kl4X7p0S4RGb3rsB08wgFe9EmSZHIgcIm+SuVo7N4qqbI85qo2ulU6J8NN7ZtgMPHzrMhzgAgf/KnqPqwDIxnNmRNJmHTUYwIDAQABozgwNjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAukH6cJUUj5faa8CuPCqrEa0PoLY4SYNnff9NI+TTAHkB9l+kOcFl5eo2EQOcWmZKYi7QLlWC4jy/KQYattO9FMaxiOQL4FAc6ZIbNyfwWBzZWyr5syYJTTTnkLq8A9pCKarN49+FqhJseycU+8EhJEJyP5pv5hLvDNTTHOQ6SXhASsiX8cjo3AY4bxA5pWeXuTZ459qDxOnQd+GrOe4dIeqflk0hA2xYKe3SfF+QlK8EO370B8Dj8RX230OATM1E3OtYyALe34KW3wM9Qm9rb0eViDnVyDiCWkhhQnw5yPg/XQfloug2itRYuCnfUoRt8xfeHgwz2Ymz8cUADn3KpTGCAf4wggH6AgEBMHgwczELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFRydXN0ZWQgVmFsaWQgQ0ECAQIwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE1MDkxMDA4MDQ0M1owIwYJKoZIhvcNAQkEMRYEFNg6lGtV9bJbL2hA0c5DdOeuCQ6lMA0GCSqGSIb3DQEBAQUABIIBAKGziwzA5Q38rIvNUDHCjYVTR1FhALGZv677Tc2+pwd82W6O9q5GG9IfkF3ajb1fquUIpGPkf7r0oiO4udC8cSehA+lfhR94A8aCM9UhzvTtRI3tFB+TPSk1UcXlX8tB7dNkx4zC06ujlSaRKkmaZODVXQFEcsF6CKMApsBuUJrwzvbQqVi2KHXUO6oGlMEyt4tY+g2OY/vyxGajfAL49dAYOTtrV0arvJvoTYh+E0iSrsbuiuAxKAVjK/QnLJoV/dTaCqW4t3lzHrpE3+avqMXiewxu84VJSURxoryY89uAZS9+4MKrSOGlGCJy/8xDIAm9pi6lPJBP2pIRjaRt9r0=\r\n";
+const header = "manifest-signature: MIIF1AYJKoZIhvcNAQcCoIIFxTCCBcECAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCA54wggOaMIICgqADAgECAgECMA0GCSqGSIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEkMCIGA1UEChMbRXhhbXBsZSBUcnVzdGVkIENvcnBvcmF0aW9uMRkwFwYDVQQDExBUcnVzdGVkIFZhbGlkIENBMB4XDTE1MDkxMDA4MDQzNVoXDTM1MDkxMDA4MDQzNVowdDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGjAYBgNVBAMTEVRydXN0ZWQgQ29ycCBDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAts8whjOzEbn/w1xkFJ67af7F/JPujBK91oyJekh2schIMzFau9pY8S1AiJQoJCulOJCJfUc8hBLKBZiGAkii+4Gpx6cVqMLe6C22MdD806Soxn8Dg4dQqbIvPuI4eeVKu5CEk80PW/BaFMmRvRHO62C7PILuH6yZeGHC4P7dTKpsk4CLxh/jRGXLC8jV2BCW0X+3BMbHBg53NoI9s1Gs7KGYnfOHbBP5wEFAa00RjHnubUaCdEBlC8Kl4X7p0S4RGb3rsB08wgFe9EmSZHIgcIm+SuVo7N4qqbI85qo2ulU6J8NN7ZtgMPHzrMhzgAgf/KnqPqwDIxnNmRNJmHTUYwIDAQABozgwNjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAukH6cJUUj5faa8CuPCqrEa0PoLY4SYNnff9NI+TTAHkB9l+kOcFl5eo2EQOcWmZKYi7QLlWC4jy/KQYattO9FMaxiOQL4FAc6ZIbNyfwWBzZWyr5syYJTTTnkLq8A9pCKarN49+FqhJseycU+8EhJEJyP5pv5hLvDNTTHOQ6SXhASsiX8cjo3AY4bxA5pWeXuTZ459qDxOnQd+GrOe4dIeqflk0hA2xYKe3SfF+QlK8EO370B8Dj8RX230OATM1E3OtYyALe34KW3wM9Qm9rb0eViDnVyDiCWkhhQnw5yPg/XQfloug2itRYuCnfUoRt8xfeHgwz2Ymz8cUADn3KpTGCAf4wggH6AgEBMHgwczELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSQwIgYDVQQKExtFeGFtcGxlIFRydXN0ZWQgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFRydXN0ZWQgVmFsaWQgQ0ECAQIwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE1MTAwMTIxMTEwNlowIwYJKoZIhvcNAQkEMRYEFHAisUYrrt+gBxYFhZ5KQQusOmN3MA0GCSqGSIb3DQEBAQUABIIBACHW4V0BsPWOvWrGOTRj6mPpNbH/JI1bN2oyqQZrpUQoaBY+BbYxO7TY4Uwe+aeIR/TTPJznOMF/dl3Bna6TPabezU4ylg7TVFI6W7zC5f5DZKp+Xv6uTX6knUzbbW1fkJqMtE8hGUzYXc3/C++Ci6kuOzrpWOhk6DpJHeUO/ioV56H0+QK/oMAjYpEsOohaPqvTPNOBhMQ0OQP3bmuJ6HcjZ/oz96PpzXUPKT1tDe6VykIYkV5NvtC8Tu2lDbYvp9ug3gyDgdyNSV47y5i/iWkzEhsAJB+9Z50wKhplnkxxVHEXkB/6tmfvExvQ28gLd/VbaEGDX2ljCaTSUjhD0o0=\r\n";
 
 const manifest = "Content-Location: manifest.webapp\r\n" +
   "Content-Type: application/x-web-app-manifest+json\r\n\r\n" +
 `{
   "name": "My App",
   "moz-resources": [
     {
       "src": "page2.html",
       "integrity": "JREF3JbXGvZ+I1KHtoz3f46ZkeIPrvXtG4VyFQrJ7II="
     },
     {
       "src": "index.html",
-      "integrity": "B5Phw8L1tpyRBkI0gwg/evy1fgtMlMq3BIY3Q8X0rYU="
+      "integrity": "zEubR310nePwd30NThIuoCxKJdnz7Mf5z+dZHUbH1SE="
     },
     {
       "src": "scripts/script.js",
       "integrity": "6TqtNArQKrrsXEQWu3D9ZD8xvDRIkhyV6zVdTcmsT5Q="
     },
     {
       "src": "scripts/library.js",
       "integrity": "TN2ByXZiaBiBCvS4MeZ02UyNi44vED+KjdjLInUl4o8="
@@ -29,17 +29,17 @@ const manifest = "Content-Location: mani
       "systemXHR": {
         "description": "Needed to download stuff"
       },
       "devicestorage:pictures": {
         "description": "Need to load pictures"
       }
     }
   ],
-  "moz-uuid": "some-uuid",
+  "package-identifier": "611FC2FE-491D-4A47-B3B3-43FBDF6F404F",
   "moz-package-location": "https://example.com/myapp/app.pak",
   "description": "A great app!"
 }`;
 
 const manifest_missing_moz_resources = "Content-Location: manifest.webapp\r\n" +
   "Content-Type: application/x-web-app-manifest+json\r\n\r\n" +
 `{
   "name": "My App",
@@ -81,16 +81,24 @@ function test_verify_manifest(aHeader, a
   return function() {
     do_test_pending();
     packagedAppUtils = Cc["@mozilla.org/network/packaged-app-utils;1"].
                        createInstance(Ci.nsIPackagedAppUtils);
     let fakeVerifier = {
       fireVerifiedEvent: function(aForManifest, aSuccess) {
         ok(aForManifest, "aForManifest should be true");
         equal(aSuccess, aShouldSucceed, "Expected verification result: " + aShouldSucceed);
+
+        // Verify packageIdentifier if it's a successful verification.
+        if (aShouldSucceed) {
+           equal(packagedAppUtils.packageIdentifier,
+                 '611FC2FE-491D-4A47-B3B3-43FBDF6F404F',
+                 'package identifier');
+        }
+
         do_test_finished();
         run_next_test();
       }
     };
     packagedAppUtils.verifyManifest(aHeader, aManifest, fakeVerifier);
   }
 }
 
--- a/security/sandbox/mac/Sandbox.mm
+++ b/security/sandbox/mac/Sandbox.mm
@@ -412,16 +412,20 @@ static const char contentSandboxRules[] 
   "; bug 1153809\n"
   "    (allow iokit-open\n"
   "        (iokit-user-client-class \"NVDVDContextTesla\")\n"
   "        (iokit-user-client-class \"Gen6DVDContext\"))\n"
   "\n"
   "; bug 1190032\n"
   "    (allow file*\n"
   "        (home-regex \"/Library/Caches/TemporaryItems/plugtmp.*\"))\n"
+  "\n"
+  "; bug 1201935\n"
+  "    (allow file-read*\n"
+  "        (home-subpath \"/Library/Caches/TemporaryItems\"))\n"
   "  )\n"
   ")\n";
 
 bool StartMacSandbox(MacSandboxInfo aInfo, std::string &aErrorMessage)
 {
   char *profile = NULL;
   if (aInfo.type == MacSandboxType_Plugin) {
     if (OSXVersion::OnLionOrLater()) {
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/mozharness/mozilla/checksums.py
@@ -0,0 +1,21 @@
+def parse_checksums_file(checksums):
+    """Parses checksums files that the build system generates and uploads:
+        https://hg.mozilla.org/mozilla-central/file/default/build/checksums.py"""
+    fileInfo = {}
+    for line in checksums.splitlines():
+        hash_, type_, size, file_ = line.split(None, 3)
+        size = int(size)
+        if size < 0:
+            raise ValueError("Found negative value (%d) for size." % size)
+        if file_ not in fileInfo:
+            fileInfo[file_] = {"hashes": {}}
+        # If the file already exists, make sure that the size matches the
+        # previous entry.
+        elif fileInfo[file_]['size'] != size:
+            raise ValueError("Found different sizes for same file %s (%s and %s)" % (file_, fileInfo[file_]['size'], size))
+        # Same goes for the hash.
+        elif type_ in fileInfo[file_]['hashes'] and fileInfo[file_]['hashes'][type_] != hash_:
+            raise ValueError("Found different %s hashes for same file %s (%s and %s)" % (type_, file_, fileInfo[file_]['hashes'][type_], hash_))
+        fileInfo[file_]['size'] = size
+        fileInfo[file_]['hashes'][type_] = hash_
+    return fileInfo
--- a/testing/mozharness/mozharness/mozilla/release.py
+++ b/testing/mozharness/mozharness/mozilla/release.py
@@ -35,15 +35,16 @@ class ReleaseMixin():
                 self.fatal("Release config file %s not found!" % c["release_config_file"])
             except RuntimeError:
                 self.fatal("Invalid release config file %s!" % c["release_config_file"])
             self.release_config['version'] = rc['version']
             self.release_config['buildnum'] = rc['buildNumber']
             self.release_config['ftp_server'] = rc['stagingServer']
             self.release_config['ftp_user'] = c.get('ftp_user', rc['hgUsername'])
             self.release_config['ftp_ssh_key'] = c.get('ftp_ssh_key', rc['hgSshKey'])
+            self.release_config['release_channel'] = rc['releaseChannel']
         else:
             self.info("No release config file; using default config.")
             for key in ('version', 'buildnum',
                         'ftp_server', 'ftp_user', 'ftp_ssh_key'):
                 self.release_config[key] = c[key]
         self.info("Release config:\n%s" % self.release_config)
         return self.release_config
--- a/testing/mozharness/mozharness/mozilla/signing.py
+++ b/testing/mozharness/mozharness/mozilla/signing.py
@@ -30,17 +30,17 @@ AndroidSignatureVerificationErrorList = 
     "explanation": "Not signed!"
 }]
 
 
 # SigningMixin {{{1
 
 class SigningMixin(BaseSigningMixin):
     """Generic signing helper methods."""
-    def query_moz_sign_cmd(self, formats='gpg'):
+    def query_moz_sign_cmd(self, formats=['gpg']):
         if 'MOZ_SIGNING_SERVERS' not in os.environ:
             self.fatal("MOZ_SIGNING_SERVERS not in env; no MOZ_SIGN_CMD for you!")
         dirs = self.query_abs_dirs()
         signing_dir = os.path.join(dirs['abs_work_dir'], 'tools', 'release', 'signing')
         cache_dir = os.path.join(dirs['abs_work_dir'], 'signing_cache')
         token = os.path.join(dirs['base_work_dir'], 'token')
         nonce = os.path.join(dirs['base_work_dir'], 'nonce')
         host_cert = os.path.join(signing_dir, 'host.cert')
@@ -49,17 +49,18 @@ class SigningMixin(BaseSigningMixin):
             python,
             os.path.join(signing_dir, 'signtool.py'),
             '--cachedir', cache_dir,
             '-t', token,
             '-n', nonce,
             '-c', host_cert,
         ]
         if formats:
-            cmd += ['-f', formats]
+            for f in formats:
+                cmd += ['-f', f]
         for h in os.environ['MOZ_SIGNING_SERVERS'].split(","):
             cmd += ['-H', h]
         return cmd
 
 
 # MobileSigningMixin {{{1
 class MobileSigningMixin(AndroidSigningMixin, SigningMixin):
     def verify_android_signature(self, apk, script=None, key_alias="nightly",
--- a/testing/mozharness/mozharness/mozilla/updates/balrog.py
+++ b/testing/mozharness/mozharness/mozilla/updates/balrog.py
@@ -85,16 +85,17 @@ class BalrogMixin(object):
 
     def submit_balrog_release_pusher(self, dirs):
         product = self.buildbot_config["properties"]["product"]
         cmd = [self.query_exe("python"), os.path.join(os.path.join(dirs['abs_tools_dir'], "scripts/updates/balrog-release-pusher.py"))]
         cmd.extend(["--build-properties", os.path.join(dirs["base_work_dir"], "balrog_props.json")])
         cmd.extend(["--buildbot-configs", "https://hg.mozilla.org/build/buildbot-configs"])
         cmd.extend(["--release-config", os.path.join(dirs['build_dir'], self.config.get("release_config_file"))])
         cmd.extend(["--credentials-file", os.path.join(dirs['base_work_dir'], self.config.get("balrog_credentials_file"))])
+        cmd.extend(["--release-channel", self.query_release_config()['release_channel']])
 
         return_codes = []
         for server in self.config["balrog_servers"]:
 
             server_args = [
                 "--api-root", server["balrog_api_root"],
                 "--username", self._query_balrog_username(server, product)
             ]
--- a/testing/mozharness/scripts/b2g_build.py
+++ b/testing/mozharness/scripts/b2g_build.py
@@ -632,17 +632,17 @@ class B2GBuild(LocalesMixin, PurgeMixin,
         self.sign_updates()
 
     def sign_updates(self):
         if 'MOZ_SIGNING_SERVERS' not in os.environ:
             self.info("Skipping signing since no MOZ_SIGNING_SERVERS set")
             return
 
         self.checkout_tools()
-        cmd = self.query_moz_sign_cmd(formats='b2gmar')
+        cmd = self.query_moz_sign_cmd(formats=['b2gmar'])
         cmd.append(self.query_marfile_path())
 
         retval = self.run_command(cmd)
         if retval != 0:
             self.fatal("failed to sign complete update", exit_code=2)
 
     def prep_upload(self):
         if not self.query_do_upload():
--- a/testing/mozharness/scripts/mobile_l10n.py
+++ b/testing/mozharness/scripts/mobile_l10n.py
@@ -161,17 +161,17 @@ class MobileSingleLocale(MockMixin, Loca
                 'buildnum': rc['buildnum']
             }
         repack_env = self.query_env(partial_env=c.get("repack_env"),
                                     replace_dict=replace_dict)
         if c.get('base_en_us_binary_url') and c.get('release_config_file'):
             rc = self.query_release_config()
             repack_env['EN_US_BINARY_URL'] = c['base_en_us_binary_url'] % replace_dict
         if 'MOZ_SIGNING_SERVERS' in os.environ:
-            repack_env['MOZ_SIGN_CMD'] = subprocess.list2cmdline(self.query_moz_sign_cmd(formats='jar'))
+            repack_env['MOZ_SIGN_CMD'] = subprocess.list2cmdline(self.query_moz_sign_cmd(formats=['jar']))
         self.repack_env = repack_env
         return self.repack_env
 
     def query_upload_env(self):
         if self.upload_env:
             return self.upload_env
         c = self.config
         replace_dict = {
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/scripts/release/antivirus.py
@@ -0,0 +1,192 @@
+from multiprocessing.pool import ThreadPool
+import os
+import re
+import sys
+import shutil
+
+sys.path.insert(1, os.path.dirname(os.path.dirname(sys.path[0])))
+
+from mozharness.base.python import VirtualenvMixin, virtualenv_config_options
+from mozharness.base.script import BaseScript
+
+
+class AntivirusScan(BaseScript, VirtualenvMixin):
+    config_options = [
+        [["--product"], {
+            "dest": "product",
+            "help": "Product being released, eg: firefox, thunderbird",
+        }],
+        [["--version"], {
+            "dest": "version",
+            "help": "Version of release, eg: 39.0b5",
+        }],
+        [["--build-number"], {
+            "dest": "build_number",
+            "help": "Build number of release, eg: 2",
+        }],
+        [["--bucket-name"], {
+            "dest": "bucket_name",
+            "help": "S3 Bucket to retrieve files from",
+        }],
+        [["--exclude"], {
+            "dest": "excludes",
+            "action": "append",
+            "help": "List of filename patterns to exclude. See script source for default",
+        }],
+        [["-d", "--download-parallelization"], {
+            "dest": "download_parallelization",
+            "default": 6,
+            "type": "int",
+            "help": "Number of concurrent file downloads",
+        }],
+        [["-s", "--scan-parallelization"], {
+            "dest": "scan_parallelization",
+            "default": 4,
+            "type": "int",
+            "help": "Number of concurrent file scans",
+        }],
+        [["--tools-repo"], {
+            "dest": "tools_repo",
+            "default": "https://hg.mozilla.org/build/tools",
+        }],
+        [["--tools-revision"], {
+            "dest": "tools_revision",
+            "help": "Revision of tools repo to use when downloading extract_and_run_command.py",
+        }],
+        ] + virtualenv_config_options
+
+    DEFAULT_EXCLUDES = [
+        r"^.*tests.*$",
+        r"^.*crashreporter.*$",
+        r"^.*\.zip(\.asc)?$",
+        r"^.*\.log$",
+        r"^.*\.txt$",
+        r"^.*\.asc$",
+        r"^.*/partner-repacks.*$",
+        r"^.*.checksums(\.asc)?$",
+        r"^.*/logs/.*$",
+        r"^.*/jsshell.*$",
+        r"^.*json$",
+        r"^.*/host.*$",
+        r"^.*/mar-tools/.*$",
+        r"^.*gecko-unsigned-unaligned.apk$",
+        r"^.*robocop.apk$",
+        r"^.*contrib.*"
+    ]
+    CACHE_DIR = 'cache'
+
+    def __init__(self):
+        BaseScript.__init__(self,
+            config_options=self.config_options,
+            require_config_file=False,
+            config={
+                "virtualenv_modules": [
+                    "boto",
+                    "redo",
+                    "mar",
+                ],
+                "virtualenv_path": "venv",
+            },
+            all_actions=[
+                "create-virtualenv",
+                "activate-virtualenv",
+                "get-extract-script",
+                "get-files",
+                "scan-files",
+                "cleanup-cache",
+            ],
+            default_actions=[
+                "create-virtualenv",
+                "activate-virtualenv",
+                "get-extract-script",
+                "get-files",
+                "scan-files",
+                "cleanup-cache",
+            ],
+        )
+        self.excludes = self.config.get('excludes', self.DEFAULT_EXCLUDES)
+        self.dest_dir = self.CACHE_DIR
+
+    def _get_candidates_prefix(self):
+        return "pub/{}/candidates/{}-candidates/build{}".format(
+            self.config['product'],
+            self.config["version"],
+            self.config["build_number"]
+        )
+
+    def _matches_exclude(self, keyname):
+        for exclude in self.excludes:
+            if re.search(exclude, keyname):
+                return True
+        return False
+
+    def get_extract_script(self):
+        """Gets a copy of extract_and_run_command.py from tools, and the supporting mar.py,
+        so that we can unpack various files for clam to scan them."""
+        remote_file = "{}/raw-file/{}/stage/extract_and_run_command.py".format(self.config["tools_repo"],
+                                                                               self.config["tools_revision"])
+        self.download_file(remote_file, file_name="extract_and_run_command.py")
+
+    def get_files(self):
+        """Pull the candidate files down from S3 for scanning, using parallel requests"""
+        from boto.s3.connection import S3Connection
+        from boto.exception import S3CopyError, S3ResponseError
+        from redo import retry
+
+        # suppress boto debug logging, it's too verbose with --loglevel=debug
+        import logging
+        logging.getLogger('boto').setLevel(logging.INFO)
+
+        self.info("Connecting to S3")
+        conn = S3Connection(anon=True)
+        self.info("Getting bucket {}".format(self.config["bucket_name"]))
+        bucket = conn.get_bucket(self.config["bucket_name"])
+
+        if os.path.exists(self.dest_dir):
+            self.info('Emptying {}'.format(self.dest_dir))
+            shutil.rmtree(self.dest_dir)
+        os.makedirs(self.dest_dir)
+
+        def worker(item):
+            source, destination = item
+
+            self.info("Downloading {} to {}".format(source, destination))
+            key = bucket.get_key(source)
+            return retry(key.get_contents_to_filename,
+                         args=(destination, ),
+                         sleeptime=5, max_sleeptime=60,
+                         retry_exceptions=(S3CopyError, S3ResponseError))
+
+        def find_release_files():
+            candidates_prefix = self._get_candidates_prefix()
+            self.info("Getting key names from candidates")
+            for key in bucket.list(prefix=candidates_prefix):
+                keyname = key.name
+                if self._matches_exclude(keyname):
+                    self.debug("Excluding {}".format(keyname))
+                else:
+                    destination = self.dest_dir + keyname.replace(candidates_prefix, '')
+                    dest_dir = os.path.dirname(destination)
+                    if not os.path.isdir(dest_dir):
+                        os.makedirs(dest_dir)
+                    yield (keyname, destination)
+
+        pool = ThreadPool(self.config["download_parallelization"])
+        pool.map(worker, find_release_files())
+
+    def scan_files(self):
+        """Scan the files we've collected. We do the download and scan concurrently to make
+        it easier to have a coherent log afterwards. Uses the venv python."""
+        self.run_command([self.query_python_path(), 'extract_and_run_command.py',
+                          '-j{}'.format(self.config['scan_parallelization']),
+                          'clamdscan', '-m', '--no-summary', '--', self.dest_dir])
+
+    def cleanup_cache(self):
+        """If we have simultaneous releases in flight an av slave may end up doing another
+        av job before being recycled, and we need to make sure the full disk is available."""
+        shutil.rmtree(self.dest_dir)
+
+
+if __name__ == "__main__":
+    myScript = AntivirusScan()
+    myScript.run_and_exit()
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/scripts/release/generate-checksums.py
@@ -0,0 +1,256 @@
+from multiprocessing.pool import ThreadPool
+import os
+from os import path
+import re
+import sys
+
+sys.path.insert(1, os.path.dirname(os.path.dirname(sys.path[0])))
+
+from mozharness.base.python import VirtualenvMixin, virtualenv_config_options
+from mozharness.base.script import BaseScript
+from mozharness.base.vcs.vcsbase import VCSMixin
+from mozharness.mozilla.checksums import parse_checksums_file
+from mozharness.mozilla.signing import SigningMixin
+
+class ChecksumsGenerator(BaseScript, VirtualenvMixin, SigningMixin, VCSMixin):
+    config_options = [
+        [["--stage-product"], {
+            "dest": "stage_product",
+            "help": "Name of product used in file server's directory structure, eg: firefox, mobile",
+        }],
+        [["--version"], {
+            "dest": "version",
+            "help": "Version of release, eg: 39.0b5",
+        }],
+        [["--build-number"], {
+            "dest": "build_number",
+            "help": "Build number of release, eg: 2",
+        }],
+        [["--bucket-name-prefix"], {
+            "dest": "bucket_name_prefix",
+            "help": "Prefix of bucket name, eg: net-mozaws-prod-delivery. This will be used to generate a full bucket name (such as net-mozaws-prod-delivery-{firefox,archive}.",
+        }],
+        [["-j", "--parallelization"], {
+            "dest": "parallelization",
+            "default": 20,
+            "type": int,
+            "help": "Number of checksums file to download concurrently",
+        }],
+        [["-f", "--format"], {
+            "dest": "formats",
+            "default": [],
+            "action": "append",
+            "help": "Format(s) to generate big checksums file for. Default: sha512",
+        }],
+        [["--include"], {
+            "dest": "includes",
+            "default": [],
+            "action": "append",
+            "help": "List of patterns to include in big checksums file. See script source for default.",
+        }],
+        [["--tools-repo"], {
+            "dest": "tools_repo",
+            "default": "https://hg.mozilla.org/build/tools",
+        }],
+        [["--gecko-repo"], {
+            "dest": "gecko_repo",
+            "help": "Gecko repo to get upload.py from, eg: https://hg.mozilla.org/releases/mozilla-beta",
+        }],
+        [["--gecko-revision"], {
+            "dest": "gecko_revision",
+            "help": "Revision of gecko repo to use when getting upload.py. Must be a valid hg revision identifier.",
+        }],
+        [["--upload-host"], {
+            "dest": "upload_host",
+            "help": "Host to upload big checksums file to, eg: stage.mozilla.org",
+        }],
+        [["--upload-user"], {
+            "dest": "upload_user",
+            "help": "Username to use when uploading, eg: ffxbld",
+        }],
+        [["--upload-ssh-key"], {
+            "dest": "upload_ssh_key",
+            "help": "SSH Key to use when uploading, eg: ~/.ssh/ffxbld_dsa",
+        }],
+    ] + virtualenv_config_options
+
+    def __init__(self):
+        BaseScript.__init__(self,
+            config_options=self.config_options,
+            require_config_file=False,
+            config={
+                "virtualenv_modules": [
+                    "boto",
+                    "redo",
+                ],
+                "virtualenv_path": "venv",
+            },
+            all_actions=[
+                "create-virtualenv",
+                "activate-virtualenv",
+                "collect-individual-checksums",
+                "create-big-checksums",
+                "sign",
+                "get-upload-script",
+                "upload",
+            ],
+            default_actions=[
+                "create-virtualenv",
+                "activate-virtualenv",
+                "collect-individual-checksums",
+                "create-big-checksums",
+                "sign",
+                "get-upload-script",
+                "upload",
+            ],
+        )
+
+        self.checksums = {}
+
+    def _pre_config_lock(self, rw_config):
+        super(ChecksumsGenerator, self)._pre_config_lock(rw_config)
+
+        # These defaults are set here rather in the config because default
+        # lists cannot be completely overidden, only appended to.
+        if not self.config.get("formats"):
+            self.config["formats"] = ["sha512"]
+
+        if not self.config.get("includes"):
+            self.config["includes"] = [
+                "^.*\.tar\.bz2$",
+                "^.*\.dmg$",
+                "^.*\.bundle$",
+                "^.*\.mar$",
+                "^.*Setup.*\.exe$",
+                "^.*\.xpi$",
+            ]
+
+    def _get_bucket_name(self):
+        suffix = "archive"
+        # Firefox has a special bucket, per https://github.com/mozilla-services/product-delivery-tools/blob/master/bucketmap.go
+        if self.config["stage_product"] == "firefox":
+            suffix = "firefox"
+
+        return "{}-{}".format(self.config["bucket_name_prefix"], suffix)
+
+    def _get_file_prefix(self):
+        return "pub/{}/candidates/{}-candidates/build{}".format(
+            self.config["stage_product"], self.config["version"], self.config["build_number"]
+        )
+
+    def _get_sums_filename(self, format_):
+        return "{}SUMS".format(format_.upper())
+
+    def collect_individual_checksums(self):
+        """This step grabs all of the small checksums files for the release,
+        filters out any unwanted files from within them, and adds the remainder
+        to self.checksums for subsequent steps to use."""
+        from boto.s3.connection import S3Connection
+
+        bucket_name = self._get_bucket_name()
+        file_prefix = self._get_file_prefix()
+        self.info("Bucket name is: {}".format(bucket_name))
+        self.info("File prefix is: {}".format(file_prefix))
+
+        self.info("Connecting to S3")
+        conn = S3Connection(anon=True)
+        self.debug("Successfully connected to S3")
+        candidates = conn.get_bucket(bucket_name)
+
+        # Temporary holding place for checksums
+        raw_checksums = []
+        def worker(item):
+            self.debug("Downloading {}".format(item))
+            # TODO: It would be nice to download the associated .asc file
+            # and verify against it.
+            sums = candidates.get_key(item).get_contents_as_string()
+            raw_checksums.append(sums)
+
+        def find_checksums_files():
+            self.info("Getting key names from bucket")
+            for key in candidates.list(prefix=file_prefix):
+                if key.key.endswith(".checksums"):
+                    self.debug("Found checksums file: {}".format(key.key))
+                    yield key.key
+                else:
+                    self.debug("Ignoring non-checksums file: {}".format(key.key))
+
+        pool = ThreadPool(self.config["parallelization"])
+        pool.map(worker, find_checksums_files())
+
+        for c in raw_checksums:
+            for f, info in parse_checksums_file(c).iteritems():
+                for pattern in self.config["includes"]:
+                    if re.search(pattern, f):
+                        if f in self.checksums:
+                            self.fatal("Found duplicate checksum entry for {}, don't know which one to pick.".format(f))
+                        if not set(self.config["formats"]) <= set(info["hashes"]):
+                            self.fatal("Missing necessary format for file {}".format(f))
+                        self.debug("Adding checksums for file: {}".format(f))
+                        self.checksums[f] = info
+                        break
+                else:
+                    self.debug("Ignoring checksums for file: {}".format(f))
+
+    def create_big_checksums(self):
+        for fmt in self.config["formats"]:
+            sums = self._get_sums_filename(fmt)
+            self.info("Creating big checksums file: {}".format(sums))
+            with open(sums, "w+") as output_file:
+                for fn in sorted(self.checksums):
+                    output_file.write("{}  {}\n".format(self.checksums[fn]["hashes"][fmt], fn))
+
+    def sign(self):
+        dirs = self.query_abs_dirs()
+
+        tools_dir = path.join(dirs["abs_work_dir"], "tools")
+        self.vcs_checkout(
+            repo=self.config["tools_repo"],
+            vcs="hgtool",
+            dest=tools_dir,
+        )
+
+        sign_cmd = self.query_moz_sign_cmd(formats=["gpg"])
+
+        for fmt in self.config["formats"]:
+            sums = self._get_sums_filename(fmt)
+            self.info("Signing big checksums file: {}".format(sums))
+            retval = self.run_command(sign_cmd + [sums])
+            if retval != 0:
+                self.fatal("Failed to sign {}".format(sums))
+
+    def get_upload_script(self):
+        remote_file = "{}/raw-file/{}/build/upload.py".format(self.config["gecko_repo"], self.config["gecko_revision"])
+        self.download_file(remote_file, file_name="upload.py")
+
+    def upload(self):
+        dirs = self.query_abs_dirs()
+
+        sys.path.append(path.dirname(path.abspath("upload.py")))
+        from upload import UploadFiles
+
+        files = []
+        for fmt in self.config["formats"]:
+            files.append(self._get_sums_filename(fmt))
+            files.append("{}.asc".format(self._get_sums_filename(fmt)))
+
+        post_upload_cmd = " ".join([
+            "post_upload.py", "-p", self.config["stage_product"], "-n", self.config["build_number"],
+            "-v", self.config["version"], "--release-to-candidates-dir", "--signed",
+            "--bucket-prefix", self.config["bucket_name_prefix"],
+        ])
+
+        UploadFiles(
+            self.config["upload_user"],
+            self.config["upload_host"],
+            "dummy",
+            files,
+            ssh_key=self.config["upload_ssh_key"],
+            base_path=dirs["abs_work_dir"],
+            upload_to_temp_dir=True,
+            post_upload_command=post_upload_cmd,
+        )
+
+if __name__ == "__main__":
+    myScript = ChecksumsGenerator()
+    myScript.run_and_exit()
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/scripts/release/push-candidate-to-releases.py
@@ -0,0 +1,171 @@
+from multiprocessing.pool import ThreadPool
+import os
+import re
+import sys
+
+sys.path.insert(1, os.path.dirname(os.path.dirname(sys.path[0])))
+
+from mozharness.base.python import VirtualenvMixin, virtualenv_config_options
+from mozharness.base.script import BaseScript
+
+
+class ReleasePusher(BaseScript, VirtualenvMixin):
+    config_options = [
+        [["--product"], {
+            "dest": "product",
+            "help": "Product being released, eg: firefox, thunderbird",
+        }],
+        [["--version"], {
+            "dest": "version",
+            "help": "Version of release, eg: 39.0b5",
+        }],
+        [["--build-number"], {
+            "dest": "build_number",
+            "help": "Build number of release, eg: 2",
+        }],
+        [["--bucket-name"], {
+            "dest": "bucket_name",
+            "help": "Bucket to copy files from candidates/ to releases/",
+        }],
+        [["--credentials"], {
+            "dest": "credentials",
+            "help": "File containing access key and secret access key",
+        }],
+        [["--exclude"], {
+            "dest": "excludes",
+            "default": [],
+            "action": "append",
+            "help": "List of patterns to exclude from copy. See script source for default.",
+        }],
+        [["-j", "--parallelization"], {
+            "dest": "parallelization",
+            "default": 20,
+            "type": "int",
+            "help": "Number of copy requests to run concurrently",
+        }],
+    ] + virtualenv_config_options
+
+    def __init__(self):
+        BaseScript.__init__(self,
+            config_options=self.config_options,
+            require_config_file=False,
+            config={
+                "virtualenv_modules": [
+                    "boto",
+                    "redo",
+                ],
+                "virtualenv_path": "venv",
+            },
+            all_actions=[
+                "create-virtualenv",
+                "activate-virtualenv",
+                "push-to-releases",
+            ],
+            default_actions=[
+                "create-virtualenv",
+                "activate-virtualenv",
+                "push-to-releases",
+            ],
+        )
+
+        # set the env var for boto to read our special config file
+        # rather than anything else we have at ~/.boto
+        os.environ["BOTO_CONFIG"] = os.path.abspath(self.config["credentials"])
+
+    def _pre_config_lock(self, rw_config):
+        super(ReleasePusher, self)._pre_config_lock(rw_config)
+
+        # This default is set here rather in the config because default
+        # lists cannot be completely overidden, only appended to.
+        if not self.config.get("excludes"):
+            self.config["excludes"] = [
+                r"^.*tests.*$",
+                r"^.*crashreporter.*$",
+                r"^.*\.zip(\.asc)?$",
+                r"^.*\.log$",
+                r"^.*\.txt$",
+                r"^.*/partner-repacks.*$",
+                r"^.*.checksums(\.asc)?$",
+                r"^.*/logs/.*$",
+                r"^.*/jsshell.*$",
+                r"^.*json$",
+                r"^.*/host.*$",
+                r"^.*/mar-tools/.*$",
+                r"^.*gecko-unsigned-unaligned.apk$",
+                r"^.*robocop.apk$",
+                r"^.*contrib.*"
+            ]
+
+    def _get_candidates_prefix(self):
+        return "pub/{}/candidates/{}-candidates/build{}".format(
+            self.config['product'],
+            self.config["version"],
+            self.config["build_number"]
+        )
+
+    def _get_releases_prefix(self):
+        return "pub/{}/releases/{}".format(
+            self.config["product"],
+            self.config["version"]
+        )
+
+    def _matches_exclude(self, keyname):
+        for exclude in self.config["excludes"]:
+            if re.search(exclude, keyname):
+                return True
+        return False
+
+    def push_to_releases(self):
+        """This step grabs the list of files in the candidates dir,
+        filters out any unwanted files from within them, and copies
+        the remainder."""
+        from boto.s3.connection import S3Connection
+        from boto.exception import S3CopyError, S3ResponseError
+        from redo import retry
+
+        # suppress boto debug logging, it's too verbose with --loglevel=debug
+        import logging
+        logging.getLogger('boto').setLevel(logging.INFO)
+
+        self.info("Connecting to S3")
+        conn = S3Connection()
+        self.info("Getting bucket {}".format(self.config["bucket_name"]))
+        bucket = conn.get_bucket(self.config["bucket_name"])
+
+        # ensure the destination is empty
+        self.info("Checking destination {} is empty".format(self._get_releases_prefix()))
+        keys = [k for k in bucket.list(prefix=self._get_releases_prefix())]
+        if keys:
+            self.fatal("Destination already exists with %s keys, aborting" %
+                       len(keys))
+
+        def worker(item):
+            source, destination = item
+
+            self.info("Copying {} to {}".format(source, destination))
+            return retry(bucket.copy_key,
+                         args=(destination,
+                               self.config["bucket_name"],
+                               source),
+                         sleeptime=5, max_sleeptime=60,
+                         retry_exceptions=(S3CopyError, S3ResponseError))
+
+        def find_release_files():
+            candidates_prefix = self._get_candidates_prefix()
+            release_prefix = self._get_releases_prefix()
+            self.info("Getting key names from candidates")
+            for key in bucket.list(prefix=candidates_prefix):
+                keyname = key.name
+                if self._matches_exclude(keyname):
+                    self.debug("Excluding {}".format(keyname))
+                else:
+                    destination = keyname.replace(candidates_prefix,
+                                                  release_prefix)
+                    yield (keyname, destination)
+
+        pool = ThreadPool(self.config["parallelization"])
+        pool.map(worker, find_release_files())
+
+if __name__ == "__main__":
+    myScript = ReleasePusher()
+    myScript.run_and_exit()
deleted file mode 100644
--- a/testing/web-platform/meta/content-security-policy/blink-contrib/shared-worker-connect-src-blocked.sub.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[shared-worker-connect-src-blocked.sub.html]
-  type: testharness
-  [Expecting alerts: ["xhr blocked"\]]
-    expected: FAIL
-
--- a/testing/web-platform/meta/html/browsers/history/the-location-interface/location_assign.html.ini
+++ b/testing/web-platform/meta/html/browsers/history/the-location-interface/location_assign.html.ini
@@ -1,3 +1,5 @@
 [location_assign.html]
   type: testharness
-  expected: TIMEOUT
+  [URL that fails to parse]
+    expected: FAIL
+
--- a/testing/web-platform/meta/url/url-constructor.html.ini
+++ b/testing/web-platform/meta/url/url-constructor.html.ini
@@ -46,22 +46,16 @@
     expected: FAIL
 
   [Parsing: <[61:24:74\]:98> against <http://example.org/foo/bar>]
     expected: FAIL
 
   [Parsing: <http:[61:27\]/:foo> against <http://example.org/foo/bar>]
     expected: FAIL
 
-  [Parsing: <http://2001::1\]> against <http://example.org/foo/bar>]
-    expected: FAIL
-
-  [Parsing: <http://2001::1\]:80> against <http://example.org/foo/bar>]
-    expected: FAIL
-
   [Parsing: <gopher:/example.com/> against <http://example.org/foo/bar>]
     expected: FAIL
 
   [Parsing: <data:/example.com/> against <http://example.org/foo/bar>]
     expected: FAIL
 
   [Parsing: <gopher:example.com/> against <http://example.org/foo/bar>]
     expected: FAIL
@@ -184,31 +178,19 @@
     expected: FAIL
 
   [Parsing: <http://a:b@/www.example.com> against <about:blank>]
     expected: FAIL
 
   [Parsing: <http://www.@pple.com> against <about:blank>]
     expected: FAIL
 
-  [Parsing: <http:@:www.example.com> against <about:blank>]
-    expected: FAIL
-
-  [Parsing: <http:/@:www.example.com> against <about:blank>]
-    expected: FAIL
-
-  [Parsing: <http://@:www.example.com> against <about:blank>]
-    expected: FAIL
-
   [Parsing: <http://:@www.example.com> against <about:blank>]
     expected: FAIL
 
-  [Parsing: <http://GOO  goo.com> against <http://other.com/>]
-    expected: FAIL
-
   [Parsing: <http://﷐zyx.com> against <http://other.com/>]
     expected: FAIL
 
   [Parsing: <http://%ef%b7%90zyx.com> against <http://other.com/>]
     expected: FAIL
 
   [Parsing: <http://%41.com> against <http://other.com/>]
     expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/mozilla/meta/service-workers/service-worker/ServiceWorkerGlobalScope/update.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[update.https.html]
-  type: testharness
-  [Update a registration on ServiceWorkerGlobalScope]
-    expected: FAIL
-
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.js
@@ -0,0 +1,25 @@
+importScripts('../../resources/test-helpers.sub.js');
+importScripts('../../resources/worker-testharness.js');
+
+var events_seen = [];
+
+self.registration.addEventListener('updatefound', function() {
+    events_seen.push('updatefound');
+  });
+
+self.addEventListener('activate', function(e) {
+    events_seen.push('activate');
+  });
+
+self.addEventListener('fetch', function(e) {
+    events_seen.push('fetch');
+    e.respondWith(new Response(events_seen));
+  });
+
+self.addEventListener('message', function(e) {
+    events_seen.push('message');
+    self.registration.update();
+  });
+
+// update() during the script evaluation should be ignored.
+self.registration.update();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/ServiceWorkerGlobalScope/resources/update-worker.py
@@ -0,0 +1,14 @@
+import os
+import time
+
+def main(request, response):
+    # update() does not bypass cache so set the max-age to 0 such that update()
+    # can find a new version in the network.
+    headers = [('Cache-Control', 'max-age: 0'),
+               ('Content-Type', 'application/javascript')]
+    with open(os.path.join(os.path.dirname(__file__),
+                           'update-worker.js'), 'r') as file:
+        script = file.read()
+    # Return a different script for each access.
+    return headers, '// %s\n%s' % (time.time(), script)
+
--- a/testing/xpcshell/remotexpcshelltests.py
+++ b/testing/xpcshell/remotexpcshelltests.py
@@ -568,17 +568,16 @@ class PathMapping:
         self.remote = remoteDir
 
 def main():
     if sys.version_info < (2,7):
         print >>sys.stderr, "Error: You must use python version 2.7 or newer but less than 3.0"
         sys.exit(1)
 
     parser = parser_remote()
-    commandline.add_logging_group(parser)
     options = parser.parse_args()
     if not options.localAPK:
         for file in os.listdir(os.path.join(options.objdir, "dist")):
             if (file.endswith(".apk") and file.startswith("fennec")):
                 options.localAPK = os.path.join(options.objdir, "dist")
                 options.localAPK = os.path.join(options.localAPK, file)
                 print >>sys.stderr, "using APK: " + options.localAPK
                 break
--- a/testing/xpcshell/runtestsb2g.py
+++ b/testing/xpcshell/runtestsb2g.py
@@ -143,17 +143,16 @@ def run_remote_xpcshell(parser, options,
             sys.exit(1)
     except:
         print "Automation Error: Exception caught while running tests"
         traceback.print_exc()
         sys.exit(1)
 
 def main():
     parser = parser_b2g()
-    commandline.add_logging_group(parser)
     options = parser.parse_args()
     log = commandline.setup_logging("Remote XPCShell",
                                     options,
                                     {"tbpl": sys.stdout})
     run_remote_xpcshell(parser, options, log)
 
 # You usually run this like :
 # python runtestsb2g.py --emulator arm --b2gpath $B2GPATH --manifest $MANIFEST [--xre-path $MOZ_HOST_BIN
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -1423,17 +1423,16 @@ class XPCShellTests(object):
             return False
 
         self.log.suite_end()
         return self.failCount == 0
 
 
 def main():
     parser = parser_desktop()
-    commandline.add_logging_group(parser)
     options = parser.parse_args()
 
     log = commandline.setup_logging("XPCShell", options, {"tbpl": sys.stdout})
 
     if options.xpcshell is None:
         print >> sys.stderr, """Must provide path to xpcshell using --xpcshell"""
 
     xpcsh = XPCShellTests(log)
--- a/testing/xpcshell/xpcshellcommandline.py
+++ b/testing/xpcshell/xpcshellcommandline.py
@@ -1,10 +1,12 @@
 import argparse
 
+from mozlog import commandline
+
 def add_common_arguments(parser):
     parser.add_argument("--app-path",
                         type=unicode, dest="appPath", default=None,
                         help="application directory (as opposed to XRE directory)")
     parser.add_argument("--interactive",
                         action="store_true", dest="interactive", default=False,
                         help="don't automatically run tests, drop to an xpcshell prompt")
     parser.add_argument("--verbose",
@@ -174,29 +176,33 @@ def add_b2g_arguments(parser):
                         help="Path to busybox binary to install on device")
 
     parser.set_defaults(remoteTestRoot="/data/local/tests",
                         dm_trans="adb")
 
 def parser_desktop():
     parser = argparse.ArgumentParser()
     add_common_arguments(parser)
+    commandline.add_logging_group(parser)
+
     return parser
 
 def parser_remote():
     parser = argparse.ArgumentParser()
     common = parser.add_argument_group("Common Options")
     add_common_arguments(common)
     remote = parser.add_argument_group("Remote Options")
     add_remote_arguments(remote)
+    commandline.add_logging_group(parser)
 
     return parser
 
 def parser_b2g():
     parser = argparse.ArgumentParser()
     common = parser.add_argument_group("Common Options")
     add_common_arguments(common)
     remote = parser.add_argument_group("Remote Options")
     add_remote_arguments(remote)
     b2g = parser.add_argument_group("B2G Options")
     add_b2g_arguments(b2g)
+    commandline.add_logging_group(parser)
 
     return parser
--- a/toolkit/components/extensions/test/mochitest/mochitest.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest.ini
@@ -1,10 +1,10 @@
 [DEFAULT]
-skip-if = os == 'android' || buildapp == 'b2g' || buildapp == 'mulet' || os == 'mac' || asan
+skip-if = os == 'android' || buildapp == 'b2g' || buildapp == 'mulet' || asan
 support-files =
   head.js
   file_WebRequest_page1.html
   file_WebRequest_page2.html
   file_image_good.png
   file_image_bad.png
   file_image_redirect.png
   file_style_good.css
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -180,16 +180,17 @@ static char const * const kCrashEventAnn
   // "IsGarbageCollecting"
   // "AvailablePageFile"
   // "AvailableVirtualMemory"
   // "SystemMemoryUsePercentage"
   // "OOMAllocationSize"
   // "TotalPageFile"
   // "TotalPhysicalMemory"
   // "TotalVirtualMemory"
+  // "MozCrashReason"
 };
 
 static const char kCrashMainID[] = "crash.main.2\n";
 
 static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
 
 static XP_CHAR* pendingDirectory;
 static XP_CHAR* crashReporterPath;
@@ -448,16 +449,23 @@ Concat(XP_CHAR* str, const XP_CHAR* toAp
   memcpy(str, toAppend, appendLen * sizeof(XP_CHAR));
   str += appendLen;
   *str = '\0';
   *size -= appendLen;
 
   return str;
 }
 
+static const char* gMozCrashReason = nullptr;
+
+void AnnotateMozCrashReason(const char* aReason)
+{
+  gMozCrashReason = aReason;
+}
+
 static size_t gOOMAllocationSize = 0;
 
 void AnnotateOOMAllocationSize(size_t size)
 {
   gOOMAllocationSize = size;
 }
 
 #ifndef XP_WIN
@@ -836,16 +844,22 @@ bool MinidumpCallback(
       WRITE_STATEX_FIELD(ullTotalPageFile, "TotalPageFile", _ui64toa);
       WRITE_STATEX_FIELD(ullAvailPageFile, "AvailablePageFile", _ui64toa);
       WRITE_STATEX_FIELD(ullTotalPhys, "TotalPhysicalMemory", _ui64toa);
       WRITE_STATEX_FIELD(ullAvailPhys, "AvailablePhysicalMemory", _ui64toa);
 
 #undef WRITE_STATEX_FIELD
     }
 #endif // XP_WIN
+
+    if (gMozCrashReason) {
+      WriteAnnotation(apiData, "MozCrashReason", gMozCrashReason);
+      WriteAnnotation(eventFile, "MozCrashReason", gMozCrashReason);
+    }
+
     if (oomAllocationSizeBuffer[0]) {
       WriteAnnotation(apiData, "OOMAllocationSize", oomAllocationSizeBuffer);
       WriteAnnotation(eventFile, "OOMAllocationSize", oomAllocationSizeBuffer);
     }
 
     if (memoryReportPath) {
       WriteLiteral(apiData, "ContainsMemoryReport=1\n");
       WriteLiteral(eventFile, "ContainsMemoryReport=1\n");
--- a/toolkit/crashreporter/nsExceptionHandler.h
+++ b/toolkit/crashreporter/nsExceptionHandler.h
@@ -66,16 +66,19 @@ nsresult SetMinidumpPath(const nsAString
 
 // AnnotateCrashReport, RemoveCrashReportAnnotation and
 // AppendAppNotesToCrashReport may be called from any thread in a chrome
 // process, but may only be called from the main thread in a content process.
 nsresult AnnotateCrashReport(const nsACString& key, const nsACString& data);
 nsresult RemoveCrashReportAnnotation(const nsACString& key);
 nsresult AppendAppNotesToCrashReport(const nsACString& data);
 
+// NOTE: If you change this definition, also change the definition in Assertions.h
+// as it is intended to be defining this same function.
+void AnnotateMozCrashReason(const char* aReason);
 void AnnotateOOMAllocationSize(size_t size);
 nsresult SetGarbageCollecting(bool collecting);
 void SetEventloopNestingLevel(uint32_t level);
 
 nsresult SetRestartArgs(int argc, char** argv);
 nsresult SetupExtraData(nsIFile* aAppDataDirectory,
                         const nsACString& aBuildID);
 bool GetLastRunCrashID(nsAString& id);