merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 22 Oct 2015 11:45:11 +0200
changeset 302326 76bd0c01d72e64ca4f261ffdb2652a91f961e930
parent 302274 00ee97fb1a6c481bfe362947563f3afecdfa820a (current diff)
parent 302325 dc161b1cddbf05e13303768d3df0db36a725333e (diff)
child 302365 23796582512a42dd726128c6ab638f28af6aa287
child 302391 b84cc63b2aa92b98e987a73b7b339a85788a3ab2
child 302436 eabcebb0e668c30b5429dfc9e92052bc6f37fe1d
push id5392
push userraliiev@mozilla.com
push dateMon, 14 Dec 2015 20:08:23 +0000
treeherdermozilla-beta@16ce8562a975 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone44.0a1
first release with
nightly linux32
76bd0c01d72e / 44.0a1 / 20151022030546 / files
nightly linux64
76bd0c01d72e / 44.0a1 / 20151022030546 / files
nightly mac
76bd0c01d72e / 44.0a1 / 20151022030546 / files
nightly win32
76bd0c01d72e / 44.0a1 / 20151022030546 / files
nightly win64
76bd0c01d72e / 44.0a1 / 20151022030546 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
configure.in
testing/web-platform/mozilla/meta/service-workers/service-worker/invalid-blobtype.https.html.ini
testing/web-platform/mozilla/meta/service-workers/service-worker/service-worker-csp-script.https.html.ini
testing/web-platform/mozilla/meta/service-workers/service-worker/stashed-ports.https.html.ini
testing/web-platform/mozilla/tests/service-workers/service-worker/resources/stashed-ports-basics.js
testing/web-platform/mozilla/tests/service-workers/service-worker/stashed-ports.https.html
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1215696 - Update mp4parse to v0.1.1
+Bug 1217261 - Update mp4parse to v0.1.2
--- a/accessible/windows/ProxyWrappers.h
+++ b/accessible/windows/ProxyWrappers.h
@@ -21,30 +21,35 @@ class ProxyAccessibleWrap : public Acces
   {
     mType = eProxyType;
     mBits.proxy = aProxy;
   }
 
   virtual void Shutdown() override
   {
     mBits.proxy = nullptr;
+    mStateFlags |= eIsDefunct;
   }
 };
 
 class HyperTextProxyAccessibleWrap : public HyperTextAccessibleWrap
 {
 public:
   HyperTextProxyAccessibleWrap(ProxyAccessible* aProxy) :
     HyperTextAccessibleWrap(nullptr, nullptr)
   {
     mType = eProxyType;
     mBits.proxy = aProxy;
   }
 
-  virtual void Shutdown() override { mBits.proxy = nullptr; }
+  virtual void Shutdown() override
+  {
+    mBits.proxy = nullptr;
+ mStateFlags |= eIsDefunct;
+  }
 };
 
 class DocProxyAccessibleWrap : public HyperTextProxyAccessibleWrap
 {
 public:
   DocProxyAccessibleWrap(ProxyAccessible* aProxy) :
     HyperTextProxyAccessibleWrap(aProxy)
   { mGenericTypes |= eDocument; }
--- a/accessible/windows/msaa/AccessibleWrap.cpp
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -118,17 +118,17 @@ AccessibleWrap::QueryInterface(REFIID ii
   else if (IID_IDispatch == iid || IID_IAccessible == iid)
     *ppv = static_cast<IAccessible*>(this);
   else if (IID_IEnumVARIANT == iid && !IsProxy()) {
     // Don't support this interface for leaf elements.
     if (!HasChildren() || nsAccUtils::MustPrune(this))
       return E_NOINTERFACE;
 
     *ppv = static_cast<IEnumVARIANT*>(new ChildrenEnumVariant(this));
-  } else if (IID_IServiceProvider == iid && !IsProxy())
+  } else if (IID_IServiceProvider == iid)
     *ppv = new ServiceProvider(this);
   else if (IID_ISimpleDOMNode == iid && !IsProxy()) {
     if (IsDefunct() || (!HasOwnContent() && !IsDoc()))
       return E_NOINTERFACE;
 
     *ppv = static_cast<ISimpleDOMNode*>(new sdnAccessible(GetNode()));
   }
 
@@ -1509,17 +1509,17 @@ AccessibleWrap::GetXPAccessibleFor(const
     Accessible* child =
 #ifdef _WIN64
       GetAccessibleInSubtree(document, static_cast<uint32_t>(aVarChild.lVal));
 #else
       document->GetAccessibleByUniqueIDInSubtree(uniqueID);
 #endif
 
     // If it is a document then just return an accessible.
-    if (IsDoc())
+    if (child && IsDoc())
       return child;
 
     // Otherwise check whether the accessible is a child (this path works for
     // ARIA documents and popups).
     Accessible* parent = child;
     while (parent && parent != document) {
       if (parent == this)
         return child;
@@ -1529,18 +1529,25 @@ AccessibleWrap::GetXPAccessibleFor(const
   }
 
   // Now see about the case that both this accessible and the target one are
   // proxied.
   uint32_t id = aVarChild.lVal;
   if (IsProxy()) {
     DocAccessibleParent* proxyDoc = Proxy()->Document();
     AccessibleWrap* wrapper = GetProxiedAccessibleInSubtree(proxyDoc, id);
+    if (!wrapper)
+      return nullptr;
+
     MOZ_ASSERT(wrapper->IsProxy());
 
+    if (proxyDoc == this->Proxy()) {
+      return wrapper;
+    }
+
     ProxyAccessible* parent = wrapper->Proxy();
     while (parent && parent != proxyDoc) {
       if (parent == this->Proxy()) {
         return wrapper;
       }
 
       parent = parent->Parent();
     }
@@ -1565,16 +1572,26 @@ AccessibleWrap::GetXPAccessibleFor(const
     if (!outerDoc) {
       continue;
     }
 
     if (outerDoc->Document() != doc) {
       continue;
     }
 
+    if (doc == this) {
+      AccessibleWrap* proxyWrapper =
+        GetProxiedAccessibleInSubtree(remoteDocs->ElementAt(i), id);
+      if (proxyWrapper) {
+        return proxyWrapper;
+      }
+
+      continue;
+    }
+
     Accessible* parent = outerDoc;
     while (parent && parent != doc) {
       if (parent == this) {
         AccessibleWrap* proxyWrapper =
           GetProxiedAccessibleInSubtree(remoteDocs->ElementAt(i), id);
         if (proxyWrapper) {
           return proxyWrapper;
         }
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -121,30 +121,31 @@ var gPrivacyPane = {
   },
 
   // HISTORY MODE
 
   /**
    * The list of preferences which affect the initial history mode settings.
    * If the auto start private browsing mode pref is active, the initial
    * history mode would be set to "Don't remember anything".
-   * If all of these preferences have their default values, and the auto-start
+   * If ALL of these preferences are set to the values that correspond
+   * to keeping some part of history, and the auto-start
    * private browsing mode is not active, the initial history mode would be
    * set to "Remember everything".
    * Otherwise, the initial history mode would be set to "Custom".
    *
-   * Extensions adding their own preferences can append their IDs to this array if needed.
+   * Extensions adding their own preferences can set values here if needed.
    */
-  prefsForDefault: [
-    "places.history.enabled",
-    "browser.formfill.enable",
-    "network.cookie.cookieBehavior",
-    "network.cookie.lifetimePolicy",
-    "privacy.sanitize.sanitizeOnShutdown"
-  ],
+  prefsForKeepingHistory: {
+    "places.history.enabled": true, // History is enabled
+    "browser.formfill.enable": true, // Form information is saved
+    "network.cookie.cookieBehavior": 0, // All cookies are enabled
+    "network.cookie.lifetimePolicy": 0, // Cookies use supplied lifetime
+    "privacy.sanitize.sanitizeOnShutdown": false, // Private date is NOT cleared on shutdown
+  },
 
   /**
    * The list of control IDs which are dependent on the auto-start private
    * browsing setting, such that in "Custom" mode they would be disabled if
    * the auto-start private browsing checkbox is checked, and enabled otherwise.
    *
    * Extensions adding their own controls can append their IDs to this array if needed.
    */
@@ -153,40 +154,39 @@ var gPrivacyPane = {
     "rememberForms",
     "keepUntil",
     "keepCookiesUntil",
     "alwaysClear",
     "clearDataSettings"
   ],
 
   /**
-   * Check whether all the preferences values are set to their default values
+   * Check whether preferences values are set to keep history
    *
    * @param aPrefs an array of pref names to check for
-   * @returns boolean true if all of the prefs are set to their default values,
+   * @returns boolean true if all of the prefs are set to keep history,
    *                  false otherwise
    */
-  _checkDefaultValues: function(aPrefs) {
-    for (let i = 0; i < aPrefs.length; ++i) {
-      let pref = document.getElementById(aPrefs[i]);
-      if (pref.value != pref.defaultValue)
+  _checkHistoryValues: function(aPrefs) {
+    for (let pref of Object.keys(aPrefs)) {
+      if (document.getElementById(pref).value != aPrefs[pref])
         return false;
     }
     return true;
   },
 
   /**
    * Initialize the history mode menulist based on the privacy preferences
    */
   initializeHistoryMode: function PPP_initializeHistoryMode()
   {
     let mode;
     let getVal = aPref => document.getElementById(aPref).value;
 
-    if (this._checkDefaultValues(this.prefsForDefault)) {
+    if (this._checkHistoryValues(this.prefsForKeepingHistory)) {
       if (getVal("browser.privatebrowsing.autostart"))
         mode = "dontremember";
       else
         mode = "remember";
     }
     else
       mode = "custom";
 
--- a/configure.in
+++ b/configure.in
@@ -63,16 +63,17 @@ GLIB_VERSION=2.22
 # The macro won't be used when compiling with earlier versions anyway.
 GLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_26
 GIO_VERSION=2.22
 PERL_VERSION=5.006
 CAIRO_VERSION=1.10
 PANGO_VERSION=1.22.0
 GTK2_VERSION=2.18.0
 GTK3_VERSION=3.4.0
+GDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_4
 WINDRES_VERSION=2.14.90
 W32API_VERSION=3.14
 GNOMEUI_VERSION=2.2.0
 GCONF_VERSION=1.2.1
 STARTUP_NOTIFICATION_VERSION=0.8
 DBUS_VERSION=0.60
 SQLITE_VERSION=3.9.1
 
@@ -4242,16 +4243,18 @@ fi
 if test "$COMPILE_ENVIRONMENT"; then
   if test "$MOZ_ENABLE_GTK3"; then
     PKG_CHECK_MODULES(MOZ_GTK3, gtk+-3.0 >= $GTK3_VERSION gtk+-unix-print-3.0 glib-2.0 gobject-2.0 $GDK_PACKAGES)
     MOZ_GTK3_CFLAGS="-I${_topsrcdir}/widget/gtk/compat-gtk3 $MOZ_GTK3_CFLAGS"
     dnl Contrary to MOZ_GTK2_LIBS, MOZ_GTK3_LIBS needs to be literally added to TK_LIBS instead
     dnl of a make reference because of how TK_LIBS is mangled in toolkit/library/moz.build
     dnl for GTK+3 builds.
     TK_LIBS=$MOZ_GTK3_LIBS
+    AC_DEFINE_UNQUOTED(GDK_VERSION_MIN_REQUIRED,$GDK_VERSION_MIN_REQUIRED)
+    AC_DEFINE_UNQUOTED(GDK_VERSION_MAX_ALLOWED,$GDK_VERSION_MIN_REQUIRED)
     GLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_32
   fi
   if test "$MOZ_ENABLE_GTK2"; then
     GLIB_VERSION_MAX_ALLOWED=$GLIB_VERSION_MIN_REQUIRED
   fi
   if test "$MOZ_ENABLE_GTK"; then
     if test "$MOZ_X11"; then
       GDK_PACKAGES=gdk-x11-2.0
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5486,16 +5486,17 @@ nsDocShell::LoadPage(nsISupports* aPageD
     newSpec.AppendLiteral("view-source:");
     newSpec.Append(spec);
 
     rv = NS_NewURI(getter_AddRefs(newUri), newSpec);
     if (NS_FAILED(rv)) {
       return rv;
     }
     shEntry->SetURI(newUri);
+    shEntry->SetOriginalURI(nullptr);
   }
 
   rv = LoadHistoryEntry(shEntry, LOAD_HISTORY);
   return rv;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetCurrentDescriptor(nsISupports** aPageDescriptor)
--- a/dom/bindings/Exceptions.cpp
+++ b/dom/bindings/Exceptions.cpp
@@ -100,17 +100,17 @@ Throw(JSContext* aCx, nsresult aRv, cons
     // Don't clobber the existing exception.
     return false;
   }
 
   CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
   nsCOMPtr<nsIException> existingException = runtime->GetPendingException();
   if (existingException) {
     nsresult nr;
-    if (NS_SUCCEEDED(existingException->GetResult(&nr)) && 
+    if (NS_SUCCEEDED(existingException->GetResult(&nr)) &&
         aRv == nr) {
       // Reuse the existing exception.
 
       // Clear pending exception
       runtime->SetPendingException(nullptr);
 
       if (!ThrowExceptionObject(aCx, existingException)) {
         // If we weren't able to throw an exception we're
@@ -382,17 +382,18 @@ NS_IMETHODIMP JSStackFrame::GetLanguageN
 // @argument [out] aCanCache whether the value can get cached.
 // @argument [out] aUseCachedValue if true, just use the cached value.
 // @argument [out] aValue the value we got from the stack.
 template<typename ReturnType, typename GetterOutParamType>
 static void
 GetValueIfNotCached(JSContext* aCx, JSObject* aStack,
                     JS::SavedFrameResult (*aPropGetter)(JSContext*,
                                                         JS::Handle<JSObject*>,
-                                                        GetterOutParamType),
+                                                        GetterOutParamType,
+                                                        JS::SavedFrameSelfHosted),
                     bool aIsCached, bool* aCanCache, bool* aUseCachedValue,
                     ReturnType aValue)
 {
   MOZ_ASSERT(aStack);
 
   JS::Rooted<JSObject*> stack(aCx, aStack);
   // Allow caching if aCx and stack are same-compartment.  Otherwise take the
   // slow path.
@@ -400,17 +401,17 @@ GetValueIfNotCached(JSContext* aCx, JSOb
   if (*aCanCache && aIsCached) {
     *aUseCachedValue = true;
     return;
   }
 
   *aUseCachedValue = false;
   JS::ExposeObjectToActiveJS(stack);
 
-  aPropGetter(aCx, stack, aValue);
+  aPropGetter(aCx, stack, aValue, JS::SavedFrameSelfHosted::Exclude);
 }
 
 NS_IMETHODIMP JSStackFrame::GetFilename(nsAString& aFilename)
 {
   if (!mStack) {
     aFilename.Truncate();
     return NS_OK;
   }
--- a/dom/fetch/Response.cpp
+++ b/dom/fetch/Response.cpp
@@ -205,17 +205,20 @@ Response::Constructor(const GlobalObject
 
     nsCOMPtr<nsIInputStream> bodyStream;
     nsCString contentType;
     aRv = ExtractByteStreamFromBody(aBody.Value(), getter_AddRefs(bodyStream), contentType);
     internalResponse->SetBody(bodyStream);
 
     if (!contentType.IsVoid() &&
         !internalResponse->Headers()->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
-      internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, aRv);
+      // Ignore Append() failing here.
+      ErrorResult error;
+      internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, error);
+      error.SuppressException();
     }
 
     if (aRv.Failed()) {
       return nullptr;
     }
   }
 
   r->SetMimeType();
--- a/dom/html/test/file_fullscreen-top-layer.html
+++ b/dom/html/test/file_fullscreen-top-layer.html
@@ -17,16 +17,17 @@
       width: 0; height: 0;
       overflow: hidden;
       opacity: 0;
       mask: url(#mask);
       clip: rect(0, 0, 0, 0);
       clip-path: url(#clipPath);
       filter: opacity(0%);
       will-change: transform;
+      perspective: 10px;
       transform: scale(0);
     }
     /* The following styles are copied from ua.css to ensure that
      * no other style change may trigger frame reconstruction */
     :root {
       overflow: hidden !important;
     }
     .two #fullscreen {
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -148,16 +148,24 @@ public:
   friend class ReRequestAudioTask;
 
   // By default, the state machine polls the reader once per second when it's
   // in buffering mode. Some readers support a promise-based mechanism by which
   // they notify the state machine when the data arrives.
   virtual bool IsWaitForDataSupported() { return false; }
   virtual RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType) { MOZ_CRASH(); }
 
+  // By default, the reader return the decoded data. Some readers support
+  // retuning demuxed data.
+  virtual bool IsDemuxOnlySupported() const { return false; }
+
+  // Configure the reader to return demuxed or decoded data
+  // upon calls to Request{Audio,Video}Data.
+  virtual void SetDemuxOnly(bool /*aDemuxedOnly*/) {}
+
   virtual bool HasAudio() = 0;
   virtual bool HasVideo() = 0;
 
   // The default implementation of AsyncReadMetadata is implemented in terms of
   // synchronous ReadMetadata() calls. Implementations may also
   // override AsyncReadMetadata to create a more proper async implementation.
   virtual RefPtr<MetadataPromise> AsyncReadMetadata();
 
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -349,16 +349,18 @@ private:
   // Returns true if the state machine has shutdown or is in the process of
   // shutting down. The decoder monitor must be held while calling this.
   bool IsShutdown();
 
   // Returns true if we're currently playing. The decoder monitor must
   // be held.
   bool IsPlaying() const;
 
+  // TODO: Those callback function may receive demuxed-only data.
+  // Need to figure out a suitable API name for this case.
   void OnAudioDecoded(MediaData* aAudioSample);
   void OnVideoDecoded(MediaData* aVideoSample);
   void OnNotDecoded(MediaData::Type aType, MediaDecoderReader::NotDecodedReason aReason);
   void OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
   {
     MOZ_ASSERT(OnTaskQueue());
     OnNotDecoded(MediaData::AUDIO_DATA, aReason);
   }
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -69,16 +69,17 @@ MediaFormatReader::MediaFormatReader(Abs
   , mDemuxerInitDone(false)
   , mLastReportedNumDecodedFrames(0)
   , mLayersBackendType(aLayersBackend)
   , mInitDone(false)
   , mSeekable(false)
   , mIsEncrypted(false)
   , mTrackDemuxersMayBlock(false)
   , mHardwareAccelerationDisabled(false)
+  , mDemuxOnly(false)
   , mVideoFrameContainer(aVideoFrameContainer)
 {
   MOZ_ASSERT(aDemuxer);
   MOZ_COUNT_CTOR(MediaFormatReader);
 }
 
 MediaFormatReader::~MediaFormatReader()
 {
@@ -565,17 +566,17 @@ MediaFormatReader::ShouldSkip(bool aSkip
   if (NS_FAILED(rv)) {
     return aSkipToNextKeyframe;
   }
   return nextKeyframe < aTimeThreshold && nextKeyframe.ToMicroseconds() >= 0;
 }
 
 RefPtr<MediaDecoderReader::VideoDataPromise>
 MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe,
-                                     int64_t aTimeThreshold)
+                                    int64_t aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty(), "No sample requests allowed while seeking");
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise(), "No duplicate sample requests");
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.mSeekRequest.Exists() ||
                         mVideo.mTimeThreshold.isSome());
   MOZ_DIAGNOSTIC_ASSERT(!mSkipRequest.Exists(), "called mid-skipping");
   MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
@@ -896,18 +897,31 @@ MediaFormatReader::RequestDemuxSamples(T
   LOGV("Requesting extra demux %s", TrackTypeToStr(aTrack));
   if (aTrack == TrackInfo::kVideoTrack) {
     DoDemuxVideo();
   } else {
     DoDemuxAudio();
   }
 }
 
+bool
+MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack,
+                                        MediaRawData* aSample)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  auto& decoder = GetDecoderData(aTrack);
+  if (NS_FAILED(decoder.mDecoder->Input(aSample))) {
+      LOG("Unable to pass frame to decoder");
+      return false;
+  }
+  return true;
+}
+
 void
-MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack,
+MediaFormatReader::HandleDemuxedSamples(TrackType aTrack,
                                         AbstractMediaDecoder::AutoNotifyDecoded& aA)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
 
   if (decoder.mQueuedSamples.IsEmpty()) {
     return;
   }
@@ -1000,22 +1014,30 @@ MediaFormatReader::DecodeDemuxedSamples(
     LOGV("Input:%lld (dts:%lld kf:%d)",
          sample->mTime, sample->mTimecode, sample->mKeyframe);
     decoder.mOutputRequested = true;
     decoder.mNumSamplesInput++;
     decoder.mSizeOfQueue++;
     if (aTrack == TrackInfo::kVideoTrack) {
       aA.mParsed++;
     }
-    if (NS_FAILED(decoder.mDecoder->Input(sample))) {
-      LOG("Unable to pass frame to decoder");
+
+    if (mDemuxOnly) {
+      ReturnOutput(sample, aTrack);
+    } else if (!DecodeDemuxedSamples(aTrack, sample)) {
       NotifyError(aTrack);
       return;
     }
+
     decoder.mQueuedSamples.RemoveElementAt(0);
+    if (mDemuxOnly) {
+      // If demuxed-only case, ReturnOutput will resolve with one demuxed data.
+      // Then we should stop doing the iteration.
+      return;
+    }
     samplesPending = true;
   }
 
   // We have serviced the decoder's request for more data.
   decoder.mInputExhausted = false;
 }
 
 void
@@ -1145,45 +1167,47 @@ MediaFormatReader::Update(TrackType aTra
   LOGV("Update(%s) ni=%d no=%d ie=%d, in:%llu out:%llu qs=%u pending:%u ahead:%d sid:%u",
        TrackTypeToStr(aTrack), needInput, needOutput, decoder.mInputExhausted,
        decoder.mNumSamplesInput, decoder.mNumSamplesOutput,
        uint32_t(size_t(decoder.mSizeOfQueue)), uint32_t(decoder.mOutput.Length()),
        !decoder.HasPromise(), decoder.mLastStreamSourceID);
 
   // Demux samples if we don't have some.
   RequestDemuxSamples(aTrack);
-  // Decode all pending demuxed samples.
-  DecodeDemuxedSamples(aTrack, a);
+
+  HandleDemuxedSamples(aTrack, a);
 }
 
 void
 MediaFormatReader::ReturnOutput(MediaData* aData, TrackType aTrack)
 {
   auto& decoder = GetDecoderData(aTrack);
   MOZ_ASSERT(decoder.HasPromise());
   if (decoder.mDiscontinuity) {
     LOGV("Setting discontinuity flag");
     decoder.mDiscontinuity = false;
     aData->mDiscontinuity = true;
   }
 
   if (aTrack == TrackInfo::kAudioTrack) {
-    AudioData* audioData = static_cast<AudioData*>(aData);
+    if (aData->mType != MediaData::RAW_DATA) {
+      AudioData* audioData = static_cast<AudioData*>(aData);
 
-    if (audioData->mChannels != mInfo.mAudio.mChannels ||
-        audioData->mRate != mInfo.mAudio.mRate) {
-      LOG("change of audio format (rate:%d->%d). "
-          "This is an unsupported configuration",
-          mInfo.mAudio.mRate, audioData->mRate);
-      mInfo.mAudio.mRate = audioData->mRate;
-      mInfo.mAudio.mChannels = audioData->mChannels;
+      if (audioData->mChannels != mInfo.mAudio.mChannels ||
+          audioData->mRate != mInfo.mAudio.mRate) {
+        LOG("change of audio format (rate:%d->%d). "
+            "This is an unsupported configuration",
+            mInfo.mAudio.mRate, audioData->mRate);
+        mInfo.mAudio.mRate = audioData->mRate;
+        mInfo.mAudio.mChannels = audioData->mChannels;
+      }
     }
-    mAudio.mPromise.Resolve(audioData, __func__);
+    mAudio.mPromise.Resolve(aData, __func__);
   } else if (aTrack == TrackInfo::kVideoTrack) {
-    mVideo.mPromise.Resolve(static_cast<VideoData*>(aData), __func__);
+    mVideo.mPromise.Resolve(aData, __func__);
   }
   LOG("Resolved data promise for %s", TrackTypeToStr(aTrack));
 }
 
 size_t
 MediaFormatReader::SizeOfVideoQueueInFrames()
 {
   return SizeOfQueue(TrackInfo::kVideoTrack);
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -84,16 +84,30 @@ public:
 
   bool VideoIsHardwareAccelerated() const override;
 
   void DisableHardwareAcceleration() override;
 
   bool IsWaitForDataSupported() override { return true; }
   RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType) override;
 
+  // MediaFormatReader supports demuxed-only mode.
+  bool IsDemuxOnlySupported() const override { return true; }
+
+  void SetDemuxOnly(bool aDemuxedOnly) override
+  {
+    if (OnTaskQueue()) {
+      mDemuxOnly = aDemuxedOnly;
+      return;
+    }
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<bool>(
+      this, &MediaDecoderReader::SetDemuxOnly, aDemuxedOnly);
+    OwnerThread()->Dispatch(r.forget());
+  }
+
   bool UseBufferingHeuristics() override
   {
     return mTrackDemuxersMayBlock;
   }
 
 #ifdef MOZ_EME
   void SetCDMProxy(CDMProxy* aProxy) override;
 #endif
@@ -118,19 +132,22 @@ private:
   // Lock for corresponding track must be held.
   void ScheduleUpdate(TrackType aTrack);
   void Update(TrackType aTrack);
   // Handle actions should more data be received.
   // Returns true if no more action is required.
   bool UpdateReceivedNewData(TrackType aTrack);
   // Called when new samples need to be demuxed.
   void RequestDemuxSamples(TrackType aTrack);
+  // Handle demuxed samples by the input behavior.
+  void HandleDemuxedSamples(TrackType aTrack,
+                            AbstractMediaDecoder::AutoNotifyDecoded& aA);
   // Decode any pending already demuxed samples.
-  void DecodeDemuxedSamples(TrackType aTrack,
-                            AbstractMediaDecoder::AutoNotifyDecoded& aA);
+  bool DecodeDemuxedSamples(TrackType aTrack,
+                            MediaRawData* aSample);
   // Drain the current decoder.
   void DrainDecoder(TrackType aTrack);
   void NotifyNewOutput(TrackType aTrack, MediaData* aSample);
   void NotifyInputExhausted(TrackType aTrack);
   void NotifyDrainComplete(TrackType aTrack);
   void NotifyError(TrackType aTrack);
   void NotifyWaitingForData(TrackType aTrack);
   void NotifyEndOfStream(TrackType aTrack);
@@ -402,16 +419,19 @@ private:
   }
   bool mIsEncrypted;
 
   // Set to true if any of our track buffers may be blocking.
   bool mTrackDemuxersMayBlock;
 
   bool mHardwareAccelerationDisabled;
 
+  // Set the demuxed-only flag.
+  Atomic<bool> mDemuxOnly;
+
   // Seeking objects.
   bool IsSeeking() const { return mPendingSeekTime.isSome(); }
   void AttemptSeek();
   void OnSeekFailed(TrackType aTrack, DemuxerFailureReason aFailure);
   void DoVideoSeek();
   void OnVideoSeekCompleted(media::TimeUnit aTime);
   void OnVideoSeekFailed(DemuxerFailureReason aFailure)
   {
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -446,17 +446,17 @@ RTCPeerConnection.prototype = {
   _initCertificate: function(certificates) {
     let certPromise;
     if (certificates && certificates.length > 0) {
       if (certificates.length > 1) {
         throw new this._win.DOMException(
           "RTCPeerConnection does not currently support multiple certificates",
           "NotSupportedError");
       }
-      let cert = certificates.find(c => c.expires.getTime() > Date.now());
+      let cert = certificates.find(c => c.expires > Date.now());
       if (!cert) {
         throw new this._win.DOMException(
           "Unable to create RTCPeerConnection with an expired certificate",
           "InvalidParameterError");
       }
       certPromise = Promise.resolve(cert);
     } else {
       certPromise = this._win.RTCPeerConnection.generateCertificate({
--- a/dom/media/gtest/MockMediaDecoderOwner.h
+++ b/dom/media/gtest/MockMediaDecoderOwner.h
@@ -24,16 +24,25 @@ public:
   {
   }
   virtual void NetworkError() override {}
   virtual void DecodeError() override {}
   virtual void LoadAborted() override {}
   virtual void PlaybackEnded() override {}
   virtual void SeekStarted() override {}
   virtual void SeekCompleted() override {}
+  virtual void DownloadProgressed() override {}
+  virtual void UpdateReadyState() override {}
+  virtual void FirstFrameLoaded() override {}
+#ifdef MOZ_EME
+  virtual void DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
+                                 const nsAString& aInitDataType) override {}
+#endif // MOZ_EME
+  virtual bool IsActive() override { return true; }
+  virtual bool IsHidden() override { return false; }
   virtual void DownloadSuspended() override {}
   virtual void DownloadResumed(bool aForceNetworkLoading) override {}
   virtual void NotifySuspendedByCache(bool aIsSuspended) override {}
   virtual void NotifyDecoderPrincipalChanged() override {}
   virtual VideoFrameContainer* GetVideoFrameContainer() override
   {
     return nullptr;
   }
--- a/dom/media/gtest/MockMediaResource.cpp
+++ b/dom/media/gtest/MockMediaResource.cpp
@@ -6,17 +6,18 @@
 
 #include <sys/types.h>
 #include <sys/stat.h>
 
 namespace mozilla
 {
 
 MockMediaResource::MockMediaResource(const char* aFileName)
-  : mFileName(aFileName)
+  : mFileHandle(nullptr)
+  , mFileName(aFileName)
   , mContentType(NS_LITERAL_CSTRING("video/mp4"))
 {
 }
 
 nsresult
 MockMediaResource::Open(nsIStreamListener** aStreamListener)
 {
   mFileHandle = fopen(mFileName, "rb");
new file mode 100644
--- /dev/null
+++ b/dom/media/gtest/TestMediaFormatReader.cpp
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TaskQueue.h"
+#include "ImageContainer.h"
+#include "Layers.h"
+#include "MediaData.h"
+#include "MediaFormatReader.h"
+#include "MP4Decoder.h"
+#include "MockMediaDecoderOwner.h"
+#include "MockMediaResource.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class MockMP4Decoder : public MP4Decoder
+{
+public:
+  MockMP4Decoder()
+    : MP4Decoder(new MockMediaDecoderOwner())
+  {}
+
+  // Avoid the assertion.
+  AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() override
+  {
+    return nullptr;
+  }
+};
+
+class MediaFormatReaderBinding
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaFormatReaderBinding);
+  RefPtr<MockMP4Decoder> mDecoder;
+  RefPtr<MockMediaResource> mResource;
+  RefPtr<MediaDecoderReader> mReader;
+  RefPtr<TaskQueue> mTaskQueue;
+  explicit MediaFormatReaderBinding(const char* aFileName = "gizmo.mp4")
+    : mDecoder(new MockMP4Decoder())
+    , mResource(new MockMediaResource(aFileName))
+    , mReader(new MediaFormatReader(mDecoder,
+                                    new MP4Demuxer(mResource),
+                                    new VideoFrameContainer(
+                                      (HTMLMediaElement*)(0x1),
+                                      layers::LayerManager::CreateImageContainer(
+                                        layers::ImageContainer::ASYNCHRONOUS))
+                                    ))
+    , mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK)))
+  {
+  }
+
+  bool Init() {
+    // Init Resource.
+    nsresult rv = mResource->Open(nullptr);
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+    mDecoder->SetResource(mResource);
+    // Init Reader.
+    rv = mReader->Init();
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+    return true;
+  }
+
+  void OnMetadataReadAudio(MetadataHolder* aMetadata)
+  {
+    EXPECT_TRUE(aMetadata);
+    mReader->RequestAudioData()
+      ->Then(mReader->OwnerThread(), __func__, this,
+             &MediaFormatReaderBinding::OnAudioRawDataDemuxed,
+             &MediaFormatReaderBinding::OnNotDemuxed);
+  }
+
+  void OnMetadataReadVideo(MetadataHolder* aMetadata)
+  {
+    EXPECT_TRUE(aMetadata);
+    mReader->RequestVideoData(true, 0)
+      ->Then(mReader->OwnerThread(), __func__, this,
+             &MediaFormatReaderBinding::OnVideoRawDataDemuxed,
+             &MediaFormatReaderBinding::OnNotDemuxed);
+  }
+
+  void OnMetadataNotRead(ReadMetadataFailureReason aReason) {
+    EXPECT_TRUE(false);
+    ReaderShutdown();
+  }
+
+  void OnAudioRawDataDemuxed(MediaData* aAudioSample)
+  {
+    EXPECT_TRUE(aAudioSample);
+    EXPECT_EQ(MediaData::RAW_DATA, aAudioSample->mType);
+    ReaderShutdown();
+  }
+
+  void OnVideoRawDataDemuxed(MediaData* aVideoSample)
+  {
+    EXPECT_TRUE(aVideoSample);
+    EXPECT_EQ(MediaData::RAW_DATA, aVideoSample->mType);
+    ReaderShutdown();
+  }
+
+  void OnNotDemuxed(MediaDecoderReader::NotDecodedReason aReason)
+  {
+    EXPECT_TRUE(false);
+    ReaderShutdown();
+  }
+
+  void ReaderShutdown()
+  {
+    RefPtr<MediaFormatReaderBinding> self = this;
+    mReader->Shutdown()
+      ->Then(mTaskQueue, __func__,
+             [self]() {
+               self->mTaskQueue->BeginShutdown();
+             },
+             [self]() {
+               EXPECT_TRUE(false);
+               self->mTaskQueue->BeginShutdown();
+             }); //Then
+  }
+  template<class Function>
+  void RunTestAndWait(Function&& aFunction)
+  {
+    RefPtr<nsRunnable> r = NS_NewRunnableFunction(Forward<Function>(aFunction));
+    mTaskQueue->Dispatch(r.forget());
+    mTaskQueue->AwaitShutdownAndIdle();
+  }
+private:
+  ~MediaFormatReaderBinding()
+  {}
+};
+
+
+template <typename T>
+T GetPref(const char* aPrefKey);
+
+template <>
+bool GetPref<bool>(const char* aPrefKey)
+{
+  return Preferences::GetBool(aPrefKey);
+}
+
+template <typename T>
+void SetPref(const char* a, T value);
+
+template <>
+void SetPref<bool>(const char* aPrefKey, bool aValue)
+{
+  unused << Preferences::SetBool(aPrefKey, aValue);
+}
+
+template <typename T>
+class PreferencesRAII
+{
+public:
+  explicit PreferencesRAII(const char* aPrefKey, T aValue)
+  : mPrefKey(aPrefKey)
+  {
+    mDefaultPref = GetPref<T>(aPrefKey);
+    SetPref(aPrefKey, aValue);
+  }
+  ~PreferencesRAII()
+  {
+    SetPref(mPrefKey, mDefaultPref);
+  }
+private:
+  T mDefaultPref;
+  const char* mPrefKey;
+};
+
+TEST(MediaFormatReader, RequestAudioRawData)
+{
+  PreferencesRAII<bool> pref =
+    PreferencesRAII<bool>("media.use-blank-decoder",
+                          true);
+  RefPtr<MediaFormatReaderBinding> b = new MediaFormatReaderBinding();
+  if (!b->Init())
+  {
+    EXPECT_TRUE(false);
+    // Stop the test since initialization failed.
+    return;
+  }
+  if (!b->mReader->IsDemuxOnlySupported())
+  {
+    EXPECT_TRUE(false);
+    // Stop the test since the reader cannot support demuxed-only demand.
+    return;
+  }
+  // Switch to demuxed-only mode.
+  b->mReader->SetDemuxOnly(true);
+  // To ensure the MediaDecoderReader::InitializationTask and
+  // MediaDecoderReader::SetDemuxOnly can be done.
+  NS_ProcessNextEvent();
+  auto testCase = [b]() {
+    InvokeAsync(b->mReader->OwnerThread(),
+                b->mReader.get(),
+                __func__,
+                &MediaDecoderReader::AsyncReadMetadata)
+      ->Then(b->mReader->OwnerThread(), __func__, b.get(),
+             &MediaFormatReaderBinding::OnMetadataReadAudio,
+             &MediaFormatReaderBinding::OnMetadataNotRead);
+  };
+  b->RunTestAndWait(testCase);
+}
+TEST(MediaFormatReader, RequestVideoRawData)
+{
+  PreferencesRAII<bool> pref =
+    PreferencesRAII<bool>("media.use-blank-decoder",
+                          true);
+  RefPtr<MediaFormatReaderBinding> b = new MediaFormatReaderBinding();
+  if (!b->Init())
+  {
+    EXPECT_TRUE(false);
+    // Stop the test since initialization failed.
+    return;
+  }
+  if (!b->mReader->IsDemuxOnlySupported())
+  {
+    EXPECT_TRUE(false);
+    // Stop the test since the reader cannot support demuxed-only demand.
+    return;
+  }
+  // Switch to demuxed-only mode.
+  b->mReader->SetDemuxOnly(true);
+  // To ensure the MediaDecoderReader::InitializationTask and
+  // MediaDecoderReader::SetDemuxOnly can be done.
+  NS_ProcessNextEvent();
+  auto testCase = [b]() {
+    InvokeAsync(b->mReader->OwnerThread(),
+                b->mReader.get(),
+                __func__,
+                &MediaDecoderReader::AsyncReadMetadata)
+      ->Then(b->mReader->OwnerThread(), __func__, b.get(),
+             &MediaFormatReaderBinding::OnMetadataReadVideo,
+             &MediaFormatReaderBinding::OnMetadataNotRead);
+  };
+  b->RunTestAndWait(testCase);
+}
\ No newline at end of file
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -7,16 +7,17 @@
 UNIFIED_SOURCES += [
     'MockMediaResource.cpp',
     'TestAudioCompactor.cpp',
     'TestGMPCrossOrigin.cpp',
     'TestGMPRemoveAndDelete.cpp',
     'TestGMPUtils.cpp',
     'TestIntervalSet.cpp',
     'TestMediaEventSource.cpp',
+    'TestMediaFormatReader.cpp',
     'TestMozPromise.cpp',
     'TestMP3Demuxer.cpp',
     'TestMP4Demuxer.cpp',
     # 'TestMP4Reader.cpp', disabled so we can turn check tests back on (bug 1175752)
     'TestTrackEncoder.cpp',
     'TestVideoSegment.cpp',
     'TestWebMBuffered.cpp',
 ]
--- a/dom/media/tests/mochitest/test_peerConnection_certificates.html
+++ b/dom/media/tests/mochitest/test_peerConnection_certificates.html
@@ -102,25 +102,25 @@
     var expiredCert;
     return Promise.resolve()
       .then(() => RTCPeerConnection.generateCertificate({
         name: "ECDSA",
         namedCurve: "P-256",
         expires: 1 // smallest possible expiration window
       }))
       .then(cert => {
-        ok(cert.expires instanceof Date, 'cert has expiration time');
-        info('Expires at ' + cert.expires);
+        ok(!isNaN(cert.expires), 'cert has expiration time');
+        info('Expires at ' + new Date(cert.expires));
         expiredCert = cert;
       })
 
       .then(() => checkBadParameters())
 
       .then(() => {
-        var delay = expiredCert.expires.getTime() - Date.now();
+        var delay = expiredCert.expires - Date.now();
         // Hopefully this delay is never needed.
         if (delay > 0) {
           return new Promise(r => setTimeout(r, delay));
         }
       })
       .then(() => {
         ok(expiredCert.expires <= Date.now(), 'Cert should be at or past expiration');
         try {
--- a/dom/media/webrtc/RTCCertificate.h
+++ b/dom/media/webrtc/RTCCertificate.h
@@ -13,20 +13,18 @@
 #include "nsNSSShutDown.h"
 #include "prtime.h"
 #include "sslt.h"
 #include "ScopedNSSTypes.h"
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/RefPtr.h"
-#include "mozilla/dom/Date.h"
 #include "mozilla/dom/CryptoKey.h"
 #include "mtransport/dtlsidentity.h"
-#include "js/Date.h"
 #include "js/StructuredClone.h"
 #include "js/TypeDecls.h"
 
 namespace mozilla {
 namespace dom {
 
 class ObjectOrString;
 
@@ -50,19 +48,19 @@ public:
                  PRTime aExpires);
 
   nsIGlobalObject* GetParentObject() const { return mGlobal; }
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL expires attribute.  Note: JS dates are milliseconds since epoch;
   // NSPR PRTime is in microseconds since the same epoch.
-  JS::ClippedTime Expires() const
+  uint64_t Expires() const
   {
-    return JS::TimeClip(mExpires / PR_USEC_PER_MSEC);
+    return mExpires / PR_USEC_PER_MSEC;
   }
 
   // Accessors for use by PeerConnectionImpl.
   RefPtr<DtlsIdentity> CreateDtlsIdentity() const;
   CERTCertificate* Certificate() const { return mCertificate; }
 
   // For nsNSSShutDownObject
   virtual void virtualDestroyNSSReference() override;
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
@@ -611,21 +611,16 @@ MulticastDNSDeviceProvider::OnServiceFou
 
   nsAutoCString serviceName;
   if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetServiceName(serviceName)))) {
     return rv;
   }
 
   LOG_I("OnServiceFound: %s", serviceName.get());
 
-  if (mRegisteredName == serviceName) {
-    LOG_I("ignore self");
-    return NS_OK;
-  }
-
   if (mMulticastDNS) {
     if (NS_WARN_IF(NS_FAILED(rv = mMulticastDNS->ResolveService(
         aServiceInfo, mWrappedListener)))) {
       return rv;
     }
   }
 
   return NS_OK;
@@ -701,18 +696,21 @@ MulticastDNSDeviceProvider::OnServiceReg
   nsAutoCString name;
   if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetServiceName(name)))) {
     return rv;
   }
 
   LOG_I("OnServiceRegistered (%s)",  name.get());
   mRegisteredName = name;
 
-  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->SetId(name)))) {
-    return rv;
+  if (mMulticastDNS) {
+    if (NS_WARN_IF(NS_FAILED(rv = mMulticastDNS->ResolveService(
+        aServiceInfo, mWrappedListener)))) {
+      return rv;
+    }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::OnServiceUnregistered(nsIDNSServiceInfo* aServiceInfo)
 {
@@ -771,16 +769,26 @@ MulticastDNSDeviceProvider::OnServiceRes
 
   LOG_I("OnServiceResolved: %s", serviceName.get());
 
   nsAutoCString host;
   if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetHost(host)))) {
     return rv;
   }
 
+  if (mRegisteredName == serviceName) {
+    LOG_I("ignore self");
+
+    if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->SetId(host)))) {
+      return rv;
+    }
+
+    return NS_OK;
+  }
+
   nsAutoCString address;
   if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetAddress(address)))) {
     return rv;
   }
 
   uint16_t port;
   if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetPort(&port)))) {
     return rv;
--- a/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
+++ b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
@@ -629,16 +629,84 @@ function noAddDevice() {
   };
   provider.listener = listener;
   provider.forceDiscovery();
   provider.listener = null;
 
   run_next_test();
 }
 
+function ignoreSelfDevice() {
+  Services.prefs.setBoolPref(PREF_DISCOVERY, false);
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
+
+  let mockDevice = createDevice("device.local",
+                                12345,
+                                "service.name",
+                                "_mozilla_papi._tcp");
+  let mockSDObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {
+      listener.onDiscoveryStarted(serviceType);
+      listener.onServiceFound(createDevice("",
+                                           0,
+                                           mockDevice.serviceName,
+                                           mockDevice.serviceType));
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {}
+      };
+    },
+    registerService: function(serviceInfo, listener) {
+      listener.onServiceRegistered(createDevice("",
+                                                0,
+                                                mockDevice.serviceName,
+                                                mockDevice.serviceType));
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {}
+      };
+    },
+    resolveService: function(serviceInfo, listener) {
+      Assert.equal(serviceInfo.serviceName, mockDevice.serviceName);
+      Assert.equal(serviceInfo.serviceType, mockDevice.serviceType);
+      listener.onServiceResolved(createDevice(mockDevice.host,
+                                              mockDevice.port,
+                                              mockDevice.serviceName,
+                                              mockDevice.serviceType));
+    }
+  };
+
+  let mockServerObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPPresentationServer]),
+    init: function() {},
+    sessionRequest: function() {},
+    close: function() {},
+    id: '',
+    port: 0,
+    listener: null,
+  };
+
+  let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj);
+  let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+  let listener = new TestPresentationDeviceListener();
+
+  // Register service
+  provider.listener = listener;
+  Assert.equal(mockServerObj.id, mockDevice.host);
+
+  // Start discovery
+  Services.prefs.setBoolPref(PREF_DISCOVERY, true);
+  Assert.equal(listener.count(), 0);
+
+  provider.listener = null;
+
+  run_next_test();
+}
 function addDeviceDynamically() {
   Services.prefs.setBoolPref(PREF_DISCOVERY, false);
 
   let mockDevice = createDevice("device.local",
                                 12345,
                                 "service.name",
                                 "_mozilla_papi._tcp");
   let mockObj = {
@@ -949,15 +1017,16 @@ function run_test() {
   add_test(registerService);
   add_test(noRegisterService);
   add_test(registerServiceDynamically);
   add_test(addDevice);
   add_test(handleSessionRequest);
   add_test(handleOnSessionRequest);
   add_test(handleOnSessionRequestFromUnknownDevice);
   add_test(noAddDevice);
+  add_test(ignoreSelfDevice);
   add_test(addDeviceDynamically);
   add_test(updateDevice);
   add_test(diffDiscovery);
   add_test(serverClosed);
 
   run_next_test();
 }
--- a/dom/webidl/RTCCertificate.webidl
+++ b/dom/webidl/RTCCertificate.webidl
@@ -3,10 +3,10 @@
  * 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/.
  *
  * Specification: http://w3c.github.io/webrtc-pc/#certificate-management
  */
 
 [Pref="media.peerconnection.enabled"]
 interface RTCCertificate {
-  readonly attribute Date expires;
+  readonly attribute DOMTimeStamp expires;
 };
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -911,16 +911,33 @@ XULDocument::AttributeWillChange(nsIDocu
     // See if we need to update our ref map.
     if (aAttribute == nsGkAtoms::ref) {
         // Might not need this, but be safe for now.
         nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
         RemoveElementFromRefMap(aElement);
     }
 }
 
+static bool
+ShouldPersistAttribute(Element* aElement, nsIAtom* aAttribute)
+{
+    if (aElement->IsXULElement(nsGkAtoms::window)) {
+        // The following attributes of xul:window should be handled in
+        // nsXULWindow::SavePersistentAttributes instead of here.
+        if (aAttribute == nsGkAtoms::screenX ||
+            aAttribute == nsGkAtoms::screenY ||
+            aAttribute == nsGkAtoms::width ||
+            aAttribute == nsGkAtoms::height ||
+            aAttribute == nsGkAtoms::sizemode) {
+            return false;
+        }
+    }
+    return true;
+}
+
 void
 XULDocument::AttributeChanged(nsIDocument* aDocument,
                               Element* aElement, int32_t aNameSpaceID,
                               nsIAtom* aAttribute, int32_t aModType,
                               const nsAttrValue* aOldValue)
 {
     NS_ASSERTION(aDocument == this, "unexpected doc");
 
@@ -990,24 +1007,24 @@ XULDocument::AttributeChanged(nsIDocumen
     bool listener, resolved;
     CheckBroadcasterHookup(aElement, &listener, &resolved);
 
     // See if there is anything we need to persist in the localstore.
     //
     // XXX Namespace handling broken :-(
     nsAutoString persist;
     aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
-    if (!persist.IsEmpty()) {
+    // Persistence of attributes of xul:window is handled in nsXULWindow.
+    if (ShouldPersistAttribute(aElement, aAttribute) && !persist.IsEmpty() &&
         // XXXldb This should check that it's a token, not just a substring.
-        if (persist.Find(nsDependentAtomString(aAttribute)) >= 0) {
-            nsContentUtils::AddScriptRunner(NS_NewRunnableMethodWithArgs
-              <nsIContent*, int32_t, nsIAtom*>
-              (this, &XULDocument::DoPersist, aElement, kNameSpaceID_None,
-               aAttribute));
-        }
+        persist.Find(nsDependentAtomString(aAttribute)) >= 0) {
+        nsContentUtils::AddScriptRunner(NS_NewRunnableMethodWithArgs
+            <nsIContent*, int32_t, nsIAtom*>
+            (this, &XULDocument::DoPersist, aElement, kNameSpaceID_None,
+            aAttribute));
     }
 }
 
 void
 XULDocument::ContentAppended(nsIDocument* aDocument,
                              nsIContent* aContainer,
                              nsIContent* aFirstNewContent,
                              int32_t aNewIndexInContainer)
--- a/extensions/spellcheck/hunspell/glue/mozHunspell.cpp
+++ b/extensions/spellcheck/hunspell/glue/mozHunspell.cpp
@@ -268,63 +268,42 @@ NS_IMETHODIMP mozHunspell::GetPersonalDi
 }
 
 NS_IMETHODIMP mozHunspell::SetPersonalDictionary(mozIPersonalDictionary * aPersonalDictionary)
 {
   mPersonalDictionary = aPersonalDictionary;
   return NS_OK;
 }
 
-struct AppendNewStruct
-{
-  char16_t **dics;
-  uint32_t count;
-  bool failed;
-};
-
-static PLDHashOperator
-AppendNewString(const nsAString& aString, nsIFile* aFile, void* aClosure)
-{
-  AppendNewStruct *ans = (AppendNewStruct*) aClosure;
-  ans->dics[ans->count] = ToNewUnicode(aString);
-  if (!ans->dics[ans->count]) {
-    ans->failed = true;
-    return PL_DHASH_STOP;
-  }
-
-  ++ans->count;
-  return PL_DHASH_NEXT;
-}
-
 NS_IMETHODIMP mozHunspell::GetDictionaryList(char16_t ***aDictionaries,
                                             uint32_t *aCount)
 {
   if (!aDictionaries || !aCount)
     return NS_ERROR_NULL_POINTER;
 
-  AppendNewStruct ans = {
-    (char16_t**) moz_xmalloc(sizeof(char16_t*) * mDictionaries.Count()),
-    0,
-    false
-  };
+  uint32_t count = 0;
+  char16_t** dicts =
+    (char16_t**) moz_xmalloc(sizeof(char16_t*) * mDictionaries.Count());
 
-  // This pointer is used during enumeration
-  mDictionaries.EnumerateRead(AppendNewString, &ans);
+  for (auto iter = mDictionaries.Iter(); !iter.Done(); iter.Next()) {
+    dicts[count] = ToNewUnicode(iter.Key());
+    if (!dicts[count]) {
+      while (count) {
+        --count;
+        free(dicts[count]);
+      }
+      free(dicts);
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
 
-  if (ans.failed) {
-    while (ans.count) {
-      --ans.count;
-      free(ans.dics[ans.count]);
-    }
-    free(ans.dics);
-    return NS_ERROR_OUT_OF_MEMORY;
+    ++count;
   }
 
-  *aDictionaries = ans.dics;
-  *aCount = ans.count;
+  *aDictionaries = dicts;
+  *aCount = count;
 
   return NS_OK;
 }
 
 void
 mozHunspell::LoadDictionaryList(bool aNotifyChildProcesses)
 {
   mDictionaries.Clear();
--- a/gfx/cairo/README
+++ b/gfx/cairo/README
@@ -207,16 +207,18 @@ fix-win32-font-assertion.patch: Bug 8386
 xlib-flush-glyphs.patch: bug 839745, flush glyphs when necessary
 
 dasharray-zero-gap.patch: bug 885585, ensure strokes get painted when the gaps in a dash array are all zero length
 
 cairo-mask-extends-bug.patch: bug 918671, sometimes when building a mask we wouldn't clear it properly. This is fixed in cairo 1.12
 
 ft-no-subpixel-if-surface-disables.patch: bug 929451, don't use subpixel aa for ft fonts on surfaces that don't support it
 
+win32-printing-axis-swap.patch: bug 1205854, workaround for Windows printer drivers that can't handle swapped X and Y axes
+
 ==== pixman patches ====
 
 pixman-android-cpu-detect.patch: Add CPU detection support for Android, where we can't reliably access /proc/self/auxv.
 
 pixman-rename-and-endian.patch: include cairo-platform.h for renaming of external symbols and endian macros
 
 NOTE: we previously supported ARM assembler on MSVC, this has been removed because of the maintenance burden
 
--- a/gfx/cairo/cairo/src/cairo-matrix.c
+++ b/gfx/cairo/cairo/src/cairo-matrix.c
@@ -873,42 +873,56 @@ cairo_bool_t
   (Note that the minor axis length is at the minimum of the above solution,
   which is just sqrt ( f - sqrt(g² + h²) ) given the symmetry of (D)).
 
 
   For another derivation of the same result, using Singular Value Decomposition,
   see doc/tutorial/src/singular.c.
 */
 
-/* determine the length of the major axis of a circle of the given radius
-   after applying the transformation matrix. */
-double
-_cairo_matrix_transformed_circle_major_axis (const cairo_matrix_t *matrix,
-					     double radius)
+/* determine the length of the major and minor axes of a circle of the given
+   radius after applying the transformation matrix. */
+void
+_cairo_matrix_transformed_circle_axes (const cairo_matrix_t *matrix,
+				       double radius,
+				       double *major,
+				       double *minor)
 {
-    double  a, b, c, d, f, g, h, i, j;
+    double  a, b, c, d, f, g, h, i, j, k;
 
     _cairo_matrix_get_affine (matrix,
                               &a, &b,
                               &c, &d,
                               NULL, NULL);
 
     i = a*a + b*b;
     j = c*c + d*d;
+    k = a*c + b*d;
 
     f = 0.5 * (i + j);
     g = 0.5 * (i - j);
-    h = a*c + b*d;
+    h = hypot (g, k);
 
-    return radius * sqrt (f + hypot (g, h));
+    if (major)
+	*major = radius * sqrt (f + h);
+    if (minor)
+	*minor = radius * sqrt (f - h);
+}
 
-    /*
-     * we don't need the minor axis length, which is
-     * double min = radius * sqrt (f - sqrt (g*g+h*h));
-     */
+/* determine the length of the major axis of a circle of the given radius
+   after applying the transformation matrix. */
+double
+_cairo_matrix_transformed_circle_major_axis (const cairo_matrix_t *matrix,
+					     double radius)
+{
+    double major;
+
+    _cairo_matrix_transformed_circle_axes (matrix, radius, &major, NULL);
+
+    return major;
 }
 
 void
 _cairo_matrix_to_pixman_matrix (const cairo_matrix_t	*matrix,
 				pixman_transform_t	*pixman_transform,
 				double xc,
 				double yc)
 {
--- a/gfx/cairo/cairo/src/cairo-win32-printing-surface.c
+++ b/gfx/cairo/cairo/src/cairo-win32-printing-surface.c
@@ -610,16 +610,17 @@ static cairo_status_t
     int x_tile, y_tile, left, right, top, bottom;
     RECT clip;
     const cairo_color_t *background_color;
     const unsigned char *mime_data;
     unsigned long mime_size;
     cairo_image_info_t mime_info;
     cairo_bool_t use_mime;
     DWORD mime_type;
+    cairo_bool_t axis_swap;
 
     /* If we can't use StretchDIBits with this surface, we can't do anything
      * here.
      */
     if (!(surface->flags & CAIRO_WIN32_SURFACE_CAN_STRETCHDIB))
 	return CAIRO_INT_STATUS_UNSUPPORTED;
 
     if (surface->content == CAIRO_CONTENT_COLOR_ALPHA)
@@ -658,39 +659,65 @@ static cairo_status_t
 							  &mime_size,
 							  &mime_info);
     }
     if (_cairo_status_is_error (status))
 	return status;
 
     use_mime = (status == CAIRO_STATUS_SUCCESS);
 
-    if (!use_mime && image->format != CAIRO_FORMAT_RGB24) {
+    m = pattern->base.matrix;
+    status = cairo_matrix_invert (&m);
+    /* _cairo_pattern_set_matrix guarantees invertibility */
+    assert (status == CAIRO_STATUS_SUCCESS);
+    cairo_matrix_multiply (&m, &m, &surface->ctm);
+    cairo_matrix_multiply (&m, &m, &surface->gdi_ctm);
+    /* Check if the matrix swaps the X and Y axes by checking if the diagonal
+     * is effectively zero. This can happen, for example, if it was composed
+     * with a rotation such as a landscape transform. Some printing devices
+     * don't support such transforms in StretchDIBits.
+     */
+    axis_swap = fabs (m.xx*image->width) < 1 && fabs (m.yy*image->height) < 1;
+
+    if (!use_mime && (image->format != CAIRO_FORMAT_RGB24 || axis_swap)) {
 	cairo_surface_t *opaque_surface;
 	cairo_surface_pattern_t image_pattern;
 	cairo_solid_pattern_t background_pattern;
+	int width = image->width, height = image->height;
 
+	if (axis_swap) {
+	    width = image->height;
+	    height = image->width;
+	}
 	opaque_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
-						     image->width,
-						     image->height);
+						     width,
+						     height);
 	if (opaque_surface->status) {
 	    status = opaque_surface->status;
 	    goto CLEANUP_OPAQUE_IMAGE;
 	}
 
-	_cairo_pattern_init_solid (&background_pattern,
-				   background_color);
-	status = _cairo_surface_paint (opaque_surface,
-				       CAIRO_OPERATOR_SOURCE,
-				       &background_pattern.base,
-				       NULL);
-	if (status)
-	    goto CLEANUP_OPAQUE_IMAGE;
+	if (image->format != CAIRO_FORMAT_RGB24) {
+	    _cairo_pattern_init_solid (&background_pattern,
+				       background_color);
+	    status = _cairo_surface_paint (opaque_surface,
+					   CAIRO_OPERATOR_SOURCE,
+					   &background_pattern.base,
+					   NULL);
+	    if (status)
+		goto CLEANUP_OPAQUE_IMAGE;
+	}
 
 	_cairo_pattern_init_for_surface (&image_pattern, &image->base);
+	if (axis_swap) {
+	    /* swap the X and Y axes to undo the axis swap in the matrix */
+	    cairo_matrix_t swap_xy = { 0, 1, 1, 0, 0, 0 };
+	    cairo_pattern_set_matrix (&image_pattern.base, &swap_xy);
+	    cairo_matrix_multiply (&m, &swap_xy, &m);
+	}
 	status = _cairo_surface_paint (opaque_surface,
 				       CAIRO_OPERATOR_OVER,
 				       &image_pattern.base,
 				       NULL);
 	_cairo_pattern_fini (&image_pattern.base);
 	if (status)
 	    goto CLEANUP_OPAQUE_IMAGE;
 
@@ -706,23 +733,16 @@ static cairo_status_t
     bi.bmiHeader.biXPelsPerMeter = PELS_72DPI;
     bi.bmiHeader.biYPelsPerMeter = PELS_72DPI;
     bi.bmiHeader.biPlanes = 1;
     bi.bmiHeader.biBitCount = 32;
     bi.bmiHeader.biCompression = use_mime ? mime_type : BI_RGB;
     bi.bmiHeader.biClrUsed = 0;
     bi.bmiHeader.biClrImportant = 0;
 
-    m = pattern->base.matrix;
-    status = cairo_matrix_invert (&m);
-    /* _cairo_pattern_set_matrix guarantees invertibility */
-    assert (status == CAIRO_STATUS_SUCCESS);
-
-    cairo_matrix_multiply (&m, &m, &surface->gdi_ctm);
-    cairo_matrix_multiply(&m, &m, &surface->ctm);
     SaveDC (surface->dc);
     _cairo_matrix_to_win32_xform (&m, &xform);
 
     if (! SetWorldTransform (surface->dc, &xform)) {
 	status = _cairo_win32_print_gdi_error ("_cairo_win32_printing_surface_paint_image_pattern");
 	goto CLEANUP_OPAQUE_IMAGE;
     }
 
@@ -1260,16 +1280,17 @@ static cairo_int_status_t
     DWORD pen_width;
     DWORD *dash_array;
     HGDIOBJ obj;
     unsigned int i;
     cairo_solid_pattern_t clear;
     cairo_matrix_t mat;
     double scale;
     double scaled_width;
+    double major, minor;
 
     status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
     if (status)
 	return status;
 
     if (op == CAIRO_OPERATOR_CLEAR) {
 	_cairo_win32_printing_surface_init_clear_color (surface, &clear);
 	source = (cairo_pattern_t*) &clear;
@@ -1350,22 +1371,40 @@ static cairo_int_status_t
     if (status)
 	return status;
 
     /*
      * Switch to user space to set line parameters
      */
     SaveDC (surface->dc);
 
-    _cairo_matrix_to_win32_xform (&mat, &xform);
-    xform.eDx = 0.0f;
-    xform.eDy = 0.0f;
+    /* Some printers don't handle transformed strokes. Avoid the transform
+     * if not required for the pen shape. Use the SVD here to find the major
+     * and minor scales then check if they differ by more than 1 device unit.
+     * If the difference is smaller, then just treat the scaling as uniform.
+     * This check ignores rotations as the pen shape is symmetric before
+     * transformation.
+     */
+    _cairo_matrix_transformed_circle_axes (&mat, scale, &major, &minor);
+    if (fabs (major - minor) > 1) {
+	/* Check if the matrix swaps the X and Y axes such that the diagonal
+	 * is nearly zero. This was observed to cause problems with XPS export.
+	 */
+	if (fabs (mat.xx) < 1e-6 && fabs (mat.yy) < 1e-6) {
+	    /* swap the X and Y axes to undo the axis swap in the matrix */
+	    cairo_matrix_t swap_xy = { 0, 1, 1, 0, 0, 0 };
+	    cairo_matrix_multiply (&mat, &swap_xy, &mat);
+	}
+	_cairo_matrix_to_win32_xform (&mat, &xform);
+	xform.eDx = 0.0f;
+	xform.eDy = 0.0f;
 
-    if (!ModifyWorldTransform (surface->dc, &xform, MWT_LEFTMULTIPLY))
-	return _cairo_win32_print_gdi_error ("_win32_surface_stroke:SetWorldTransform");
+	if (!ModifyWorldTransform (surface->dc, &xform, MWT_LEFTMULTIPLY))
+	    return _cairo_win32_print_gdi_error ("_win32_surface_stroke:SetWorldTransform");
+    }
 
     if (source->type == CAIRO_PATTERN_TYPE_SOLID) {
 	StrokePath (surface->dc);
     } else {
 	if (!WidenPath (surface->dc))
 	    return _cairo_win32_print_gdi_error ("_win32_surface_stroke:WidenPath");
 	if (!SelectClipPath (surface->dc, RGN_AND))
 	    return _cairo_win32_print_gdi_error ("_win32_surface_stroke:SelectClipPath");
--- a/gfx/cairo/cairo/src/cairoint.h
+++ b/gfx/cairo/cairo/src/cairoint.h
@@ -2115,16 +2115,22 @@ cairo_private cairo_bool_t
 				     int *itx, int *ity);
 
 cairo_private cairo_bool_t
 _cairo_matrix_has_unity_scale (const cairo_matrix_t *matrix);
 
 cairo_private cairo_bool_t
 _cairo_matrix_is_pixel_exact (const cairo_matrix_t *matrix) cairo_pure;
 
+cairo_private void
+_cairo_matrix_transformed_circle_axes (const cairo_matrix_t *matrix,
+				       double radius,
+				       double *major,
+				       double *minor);
+
 cairo_private double
 _cairo_matrix_transformed_circle_major_axis (const cairo_matrix_t *matrix,
 					     double radius) cairo_pure;
 
 cairo_private void
 _cairo_matrix_to_pixman_matrix (const cairo_matrix_t	*matrix,
 				pixman_transform_t	*pixman_transform,
 				double                   xc,
new file mode 100644
--- /dev/null
+++ b/gfx/cairo/win32-printing-axis-swap.patch
@@ -0,0 +1,292 @@
+# HG changeset patch
+# User Lee Salzman <lsalzman@mozilla.com>
+# Date 1445463645 14400
+#      Wed Oct 21 17:40:45 2015 -0400
+# Node ID 9e84563cbd73c5b0993dfd018ca25b660b667e94
+# Parent  2d3fd51c4182c253a2f102655e8e9e466032853f
+workaround for Windows printer drivers that can't handle swapped X and Y axes
+
+diff --git a/gfx/cairo/cairo/src/cairo-matrix.c b/gfx/cairo/cairo/src/cairo-matrix.c
+--- a/gfx/cairo/cairo/src/cairo-matrix.c
++++ b/gfx/cairo/cairo/src/cairo-matrix.c
+@@ -873,42 +873,56 @@ cairo_bool_t
+   (Note that the minor axis length is at the minimum of the above solution,
+   which is just sqrt ( f - sqrt(g² + h²) ) given the symmetry of (D)).
+ 
+ 
+   For another derivation of the same result, using Singular Value Decomposition,
+   see doc/tutorial/src/singular.c.
+ */
+ 
+-/* determine the length of the major axis of a circle of the given radius
+-   after applying the transformation matrix. */
+-double
+-_cairo_matrix_transformed_circle_major_axis (const cairo_matrix_t *matrix,
+-					     double radius)
++/* determine the length of the major and minor axes of a circle of the given
++   radius after applying the transformation matrix. */
++void
++_cairo_matrix_transformed_circle_axes (const cairo_matrix_t *matrix,
++				       double radius,
++				       double *major,
++				       double *minor)
+ {
+-    double  a, b, c, d, f, g, h, i, j;
++    double  a, b, c, d, f, g, h, i, j, k;
+ 
+     _cairo_matrix_get_affine (matrix,
+                               &a, &b,
+                               &c, &d,
+                               NULL, NULL);
+ 
+     i = a*a + b*b;
+     j = c*c + d*d;
++    k = a*c + b*d;
+ 
+     f = 0.5 * (i + j);
+     g = 0.5 * (i - j);
+-    h = a*c + b*d;
++    h = hypot (g, k);
+ 
+-    return radius * sqrt (f + hypot (g, h));
++    if (major)
++	*major = radius * sqrt (f + h);
++    if (minor)
++	*minor = radius * sqrt (f - h);
++}
+ 
+-    /*
+-     * we don't need the minor axis length, which is
+-     * double min = radius * sqrt (f - sqrt (g*g+h*h));
+-     */
++/* determine the length of the major axis of a circle of the given radius
++   after applying the transformation matrix. */
++double
++_cairo_matrix_transformed_circle_major_axis (const cairo_matrix_t *matrix,
++					     double radius)
++{
++    double major;
++
++    _cairo_matrix_transformed_circle_axes (matrix, radius, &major, NULL);
++
++    return major;
+ }
+ 
+ void
+ _cairo_matrix_to_pixman_matrix (const cairo_matrix_t	*matrix,
+ 				pixman_transform_t	*pixman_transform,
+ 				double xc,
+ 				double yc)
+ {
+diff --git a/gfx/cairo/cairo/src/cairo-win32-printing-surface.c b/gfx/cairo/cairo/src/cairo-win32-printing-surface.c
+--- a/gfx/cairo/cairo/src/cairo-win32-printing-surface.c
++++ b/gfx/cairo/cairo/src/cairo-win32-printing-surface.c
+@@ -610,16 +610,17 @@ static cairo_status_t
+     int x_tile, y_tile, left, right, top, bottom;
+     RECT clip;
+     const cairo_color_t *background_color;
+     const unsigned char *mime_data;
+     unsigned long mime_size;
+     cairo_image_info_t mime_info;
+     cairo_bool_t use_mime;
+     DWORD mime_type;
++    cairo_bool_t axis_swap;
+ 
+     /* If we can't use StretchDIBits with this surface, we can't do anything
+      * here.
+      */
+     if (!(surface->flags & CAIRO_WIN32_SURFACE_CAN_STRETCHDIB))
+ 	return CAIRO_INT_STATUS_UNSUPPORTED;
+ 
+     if (surface->content == CAIRO_CONTENT_COLOR_ALPHA)
+@@ -658,39 +659,65 @@ static cairo_status_t
+ 							  &mime_size,
+ 							  &mime_info);
+     }
+     if (_cairo_status_is_error (status))
+ 	return status;
+ 
+     use_mime = (status == CAIRO_STATUS_SUCCESS);
+ 
+-    if (!use_mime && image->format != CAIRO_FORMAT_RGB24) {
++    m = pattern->base.matrix;
++    status = cairo_matrix_invert (&m);
++    /* _cairo_pattern_set_matrix guarantees invertibility */
++    assert (status == CAIRO_STATUS_SUCCESS);
++    cairo_matrix_multiply (&m, &m, &surface->ctm);
++    cairo_matrix_multiply (&m, &m, &surface->gdi_ctm);
++    /* Check if the matrix swaps the X and Y axes by checking if the diagonal
++     * is effectively zero. This can happen, for example, if it was composed
++     * with a rotation such as a landscape transform. Some printing devices
++     * don't support such transforms in StretchDIBits.
++     */
++    axis_swap = fabs (m.xx*image->width) < 1 && fabs (m.yy*image->height) < 1;
++
++    if (!use_mime && (image->format != CAIRO_FORMAT_RGB24 || axis_swap)) {
+ 	cairo_surface_t *opaque_surface;
+ 	cairo_surface_pattern_t image_pattern;
+ 	cairo_solid_pattern_t background_pattern;
++	int width = image->width, height = image->height;
+ 
++	if (axis_swap) {
++	    width = image->height;
++	    height = image->width;
++	}
+ 	opaque_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+-						     image->width,
+-						     image->height);
++						     width,
++						     height);
+ 	if (opaque_surface->status) {
+ 	    status = opaque_surface->status;
+ 	    goto CLEANUP_OPAQUE_IMAGE;
+ 	}
+ 
+-	_cairo_pattern_init_solid (&background_pattern,
+-				   background_color);
+-	status = _cairo_surface_paint (opaque_surface,
+-				       CAIRO_OPERATOR_SOURCE,
+-				       &background_pattern.base,
+-				       NULL);
+-	if (status)
+-	    goto CLEANUP_OPAQUE_IMAGE;
++	if (image->format != CAIRO_FORMAT_RGB24) {
++	    _cairo_pattern_init_solid (&background_pattern,
++				       background_color);
++	    status = _cairo_surface_paint (opaque_surface,
++					   CAIRO_OPERATOR_SOURCE,
++					   &background_pattern.base,
++					   NULL);
++	    if (status)
++		goto CLEANUP_OPAQUE_IMAGE;
++	}
+ 
+ 	_cairo_pattern_init_for_surface (&image_pattern, &image->base);
++	if (axis_swap) {
++	    /* swap the X and Y axes to undo the axis swap in the matrix */
++	    cairo_matrix_t swap_xy = { 0, 1, 1, 0, 0, 0 };
++	    cairo_pattern_set_matrix (&image_pattern.base, &swap_xy);
++	    cairo_matrix_multiply (&m, &swap_xy, &m);
++	}
+ 	status = _cairo_surface_paint (opaque_surface,
+ 				       CAIRO_OPERATOR_OVER,
+ 				       &image_pattern.base,
+ 				       NULL);
+ 	_cairo_pattern_fini (&image_pattern.base);
+ 	if (status)
+ 	    goto CLEANUP_OPAQUE_IMAGE;
+ 
+@@ -706,23 +733,16 @@ static cairo_status_t
+     bi.bmiHeader.biXPelsPerMeter = PELS_72DPI;
+     bi.bmiHeader.biYPelsPerMeter = PELS_72DPI;
+     bi.bmiHeader.biPlanes = 1;
+     bi.bmiHeader.biBitCount = 32;
+     bi.bmiHeader.biCompression = use_mime ? mime_type : BI_RGB;
+     bi.bmiHeader.biClrUsed = 0;
+     bi.bmiHeader.biClrImportant = 0;
+ 
+-    m = pattern->base.matrix;
+-    status = cairo_matrix_invert (&m);
+-    /* _cairo_pattern_set_matrix guarantees invertibility */
+-    assert (status == CAIRO_STATUS_SUCCESS);
+-
+-    cairo_matrix_multiply (&m, &m, &surface->gdi_ctm);
+-    cairo_matrix_multiply(&m, &m, &surface->ctm);
+     SaveDC (surface->dc);
+     _cairo_matrix_to_win32_xform (&m, &xform);
+ 
+     if (! SetWorldTransform (surface->dc, &xform)) {
+ 	status = _cairo_win32_print_gdi_error ("_cairo_win32_printing_surface_paint_image_pattern");
+ 	goto CLEANUP_OPAQUE_IMAGE;
+     }
+ 
+@@ -1260,16 +1280,17 @@ static cairo_int_status_t
+     DWORD pen_width;
+     DWORD *dash_array;
+     HGDIOBJ obj;
+     unsigned int i;
+     cairo_solid_pattern_t clear;
+     cairo_matrix_t mat;
+     double scale;
+     double scaled_width;
++    double major, minor;
+ 
+     status = _cairo_surface_clipper_set_clip (&surface->clipper, clip);
+     if (status)
+ 	return status;
+ 
+     if (op == CAIRO_OPERATOR_CLEAR) {
+ 	_cairo_win32_printing_surface_init_clear_color (surface, &clear);
+ 	source = (cairo_pattern_t*) &clear;
+@@ -1350,22 +1371,40 @@ static cairo_int_status_t
+     if (status)
+ 	return status;
+ 
+     /*
+      * Switch to user space to set line parameters
+      */
+     SaveDC (surface->dc);
+ 
+-    _cairo_matrix_to_win32_xform (&mat, &xform);
+-    xform.eDx = 0.0f;
+-    xform.eDy = 0.0f;
++    /* Some printers don't handle transformed strokes. Avoid the transform
++     * if not required for the pen shape. Use the SVD here to find the major
++     * and minor scales then check if they differ by more than 1 device unit.
++     * If the difference is smaller, then just treat the scaling as uniform.
++     * This check ignores rotations as the pen shape is symmetric before
++     * transformation.
++     */
++    _cairo_matrix_transformed_circle_axes (&mat, scale, &major, &minor);
++    if (fabs (major - minor) > 1) {
++	/* Check if the matrix swaps the X and Y axes such that the diagonal
++	 * is nearly zero. This was observed to cause problems with XPS export.
++	 */
++	if (fabs (mat.xx) < 1e-6 && fabs (mat.yy) < 1e-6) {
++	    /* swap the X and Y axes to undo the axis swap in the matrix */
++	    cairo_matrix_t swap_xy = { 0, 1, 1, 0, 0, 0 };
++	    cairo_matrix_multiply (&mat, &swap_xy, &mat);
++	}
++	_cairo_matrix_to_win32_xform (&mat, &xform);
++	xform.eDx = 0.0f;
++	xform.eDy = 0.0f;
+ 
+-    if (!ModifyWorldTransform (surface->dc, &xform, MWT_LEFTMULTIPLY))
+-	return _cairo_win32_print_gdi_error ("_win32_surface_stroke:SetWorldTransform");
++	if (!ModifyWorldTransform (surface->dc, &xform, MWT_LEFTMULTIPLY))
++	    return _cairo_win32_print_gdi_error ("_win32_surface_stroke:SetWorldTransform");
++    }
+ 
+     if (source->type == CAIRO_PATTERN_TYPE_SOLID) {
+ 	StrokePath (surface->dc);
+     } else {
+ 	if (!WidenPath (surface->dc))
+ 	    return _cairo_win32_print_gdi_error ("_win32_surface_stroke:WidenPath");
+ 	if (!SelectClipPath (surface->dc, RGN_AND))
+ 	    return _cairo_win32_print_gdi_error ("_win32_surface_stroke:SelectClipPath");
+diff --git a/gfx/cairo/cairo/src/cairoint.h b/gfx/cairo/cairo/src/cairoint.h
+--- a/gfx/cairo/cairo/src/cairoint.h
++++ b/gfx/cairo/cairo/src/cairoint.h
+@@ -2115,16 +2115,22 @@ cairo_private cairo_bool_t
+ 				     int *itx, int *ity);
+ 
+ cairo_private cairo_bool_t
+ _cairo_matrix_has_unity_scale (const cairo_matrix_t *matrix);
+ 
+ cairo_private cairo_bool_t
+ _cairo_matrix_is_pixel_exact (const cairo_matrix_t *matrix) cairo_pure;
+ 
++cairo_private void
++_cairo_matrix_transformed_circle_axes (const cairo_matrix_t *matrix,
++				       double radius,
++				       double *major,
++				       double *minor);
++
+ cairo_private double
+ _cairo_matrix_transformed_circle_major_axis (const cairo_matrix_t *matrix,
+ 					     double radius) cairo_pure;
+ 
+ cairo_private void
+ _cairo_matrix_to_pixman_matrix (const cairo_matrix_t	*matrix,
+ 				pixman_transform_t	*pixman_transform,
+ 				double                   xc,
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -129,23 +129,29 @@ void
 AsyncCompositionManager::ResolveRefLayers(CompositorParent* aCompositor,
                                           bool* aHasRemoteContent,
                                           bool* aResolvePlugins)
 {
   if (aHasRemoteContent) {
     *aHasRemoteContent = false;
   }
 
+  // If valid *aResolvePlugins indicates if we need to update plugin geometry
+  // when we walk the tree.
+  bool willResolvePlugins = (aResolvePlugins && *aResolvePlugins);
   if (!mLayerManager->GetRoot()) {
+    // Updated the return value since this result controls completing composition.
+    if (aResolvePlugins) {
+      *aResolvePlugins = false;
+    }
     return;
   }
 
   mReadyForCompose = true;
   bool hasRemoteContent = false;
-  bool willResolvePlugins = (aResolvePlugins && *aResolvePlugins);
   bool didResolvePlugins = false;
   WalkTheTree<Resolve>(mLayerManager->GetRoot(),
                        mReadyForCompose,
                        mTargetConfig,
                        aCompositor,
                        hasRemoteContent,
                        willResolvePlugins,
                        didResolvePlugins);
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -2132,17 +2132,23 @@ CompositorParent::UpdatePluginWindowStat
         }
         mPluginsLayerOffset = offset;
         mPluginsLayerVisibleRegion = visibleRegion;
         unused <<
           lts.mParent->SendUpdatePluginConfigurations(offset, visibleRegion,
                                                       lts.mPluginData);
         lts.mUpdatedPluginDataAvailable = false;
         PLUGINS_LOG("[%" PRIu64 "] updated", aId);
+      } else {
+        PLUGINS_LOG("[%" PRIu64 "] no visibility data", aId);
+        return false;
       }
+    } else {
+      PLUGINS_LOG("[%" PRIu64 "] no content root", aId);
+      return false;
     }
   }
 
   mLastPluginUpdateLayerTreeId = aId;
   mCachedPluginData = lts.mPluginData;
   return true;
 }
 #endif // #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
--- a/gfx/thebes/gfxASurface.cpp
+++ b/gfx/thebes/gfxASurface.cpp
@@ -419,17 +419,18 @@ gfxASurface::CheckSurfaceSize(const IntS
 
     return true;
 }
 
 /* static */
 int32_t
 gfxASurface::FormatStrideForWidth(gfxImageFormat format, int32_t width)
 {
-    return cairo_format_stride_for_width((cairo_format_t)(int)format, (int)width);
+    cairo_format_t cformat = gfxImageFormatToCairoFormat(format);
+    return cairo_format_stride_for_width(cformat, (int)width);
 }
 
 nsresult
 gfxASurface::BeginPrinting(const nsAString& aTitle, const nsAString& aPrintToFileName)
 {
     return NS_OK;
 }
 
@@ -462,17 +463,16 @@ gfxASurface::ContentFromFormat(gfxImageF
 {
     switch (format) {
         case gfxImageFormat::ARGB32:
             return gfxContentType::COLOR_ALPHA;
         case gfxImageFormat::RGB24:
         case gfxImageFormat::RGB16_565:
             return gfxContentType::COLOR;
         case gfxImageFormat::A8:
-        case gfxImageFormat::A1:
             return gfxContentType::ALPHA;
 
         case gfxImageFormat::Unknown:
         default:
             return gfxContentType::COLOR;
     }
 }
 
@@ -669,18 +669,16 @@ gfxASurface::BytesPerPixel(gfxImageForma
     case gfxImageFormat::ARGB32:
       return 4;
     case gfxImageFormat::RGB24:
       return 4;
     case gfxImageFormat::RGB16_565:
       return 2;
     case gfxImageFormat::A8:
       return 1;
-    case gfxImageFormat::A1:
-      return 1; // Close enough
     case gfxImageFormat::Unknown:
     default:
       NS_NOTREACHED("Not really sure what you want me to say here");
       return 0;
   }
 }
 
 void
--- a/gfx/thebes/gfxBaseSharedMemorySurface.h
+++ b/gfx/thebes/gfxBaseSharedMemorySurface.h
@@ -72,18 +72,18 @@ public:
      */
     static already_AddRefed<Sub>
     Open(const Shmem& aShmem)
     {
         SharedImageInfo* shmInfo = GetShmInfoPtr(aShmem);
         mozilla::gfx::IntSize size(shmInfo->width, shmInfo->height);
         if (!gfxASurface::CheckSurfaceSize(size))
             return nullptr;
-       
-        gfxImageFormat format = (gfxImageFormat)shmInfo->format;
+
+        gfxImageFormat format = shmInfo->format;
         long stride = gfxImageSurface::ComputeStride(size, format);
 
         RefPtr<Sub> s =
             new Sub(size,
                     stride,
                     format,
                     aShmem);
         // We didn't create this Shmem and so don't free it on errors
--- a/gfx/thebes/gfxImageSurface.cpp
+++ b/gfx/thebes/gfxImageSurface.cpp
@@ -27,17 +27,17 @@ gfxImageSurface::gfxImageSurface()
 }
 
 void
 gfxImageSurface::InitFromSurface(cairo_surface_t *csurf)
 {
     mSize.width = cairo_image_surface_get_width(csurf);
     mSize.height = cairo_image_surface_get_height(csurf);
     mData = cairo_image_surface_get_data(csurf);
-    mFormat = (gfxImageFormat) cairo_image_surface_get_format(csurf);
+    mFormat = gfxCairoFormatToImageFormat(cairo_image_surface_get_format(csurf));
     mOwnsData = false;
     mStride = cairo_image_surface_get_stride(csurf);
 
     Init(csurf, true);
 }
 
 gfxImageSurface::gfxImageSurface(unsigned char *aData, const IntSize& aSize,
                                  long aStride, gfxImageFormat aFormat)
@@ -61,19 +61,20 @@ gfxImageSurface::InitWithData(unsigned c
     mOwnsData = false;
     mData = aData;
     mFormat = aFormat;
     mStride = aStride;
 
     if (!CheckSurfaceSize(aSize))
         MakeInvalid();
 
+    cairo_format_t cformat = gfxImageFormatToCairoFormat(mFormat);
     cairo_surface_t *surface =
         cairo_image_surface_create_for_data((unsigned char*)mData,
-                                            (cairo_format_t)(int)mFormat,
+                                            cformat,
                                             mSize.width,
                                             mSize.height,
                                             mStride);
 
     // cairo_image_surface_create_for_data can return a 'null' surface
     // in out of memory conditions. The gfxASurface::Init call checks
     // the surface it receives to see if there is an error with the
     // surface and handles it appropriately. That is why there is
@@ -130,19 +131,20 @@ gfxImageSurface::AllocateAndInit(long aS
         if (!mData)
             return;
         if (aClear)
             memset(mData, 0, aMinimalAllocation);
     }
 
     mOwnsData = true;
 
+    cairo_format_t cformat = gfxImageFormatToCairoFormat(mFormat);
     cairo_surface_t *surface =
         cairo_image_surface_create_for_data((unsigned char*)mData,
-                                            (cairo_format_t)(int)mFormat,
+                                            cformat,
                                             mSize.width,
                                             mSize.height,
                                             mStride);
 
     Init(surface);
 
     if (mSurfaceValid) {
         RecordMemoryUsed(mSize.height * ComputeStride() +
@@ -157,17 +159,17 @@ gfxImageSurface::gfxImageSurface(const I
     AllocateAndInit(aStride, aExtraBytes, aClear);
 }
 
 gfxImageSurface::gfxImageSurface(cairo_surface_t *csurf)
 {
     mSize.width = cairo_image_surface_get_width(csurf);
     mSize.height = cairo_image_surface_get_height(csurf);
     mData = cairo_image_surface_get_data(csurf);
-    mFormat = (gfxImageFormat) cairo_image_surface_get_format(csurf);
+    mFormat = gfxCairoFormatToImageFormat(cairo_image_surface_get_format(csurf));
     mOwnsData = false;
     mStride = cairo_image_surface_get_stride(csurf);
 
     Init(csurf, true);
 }
 
 gfxImageSurface::~gfxImageSurface()
 {
@@ -183,19 +185,17 @@ gfxImageSurface::ComputeStride(const Int
     if (aFormat == gfxImageFormat::ARGB32)
         stride = aSize.width * 4;
     else if (aFormat == gfxImageFormat::RGB24)
         stride = aSize.width * 4;
     else if (aFormat == gfxImageFormat::RGB16_565)
         stride = aSize.width * 2;
     else if (aFormat == gfxImageFormat::A8)
         stride = aSize.width;
-    else if (aFormat == gfxImageFormat::A1) {
-        stride = (aSize.width + 7) / 8;
-    } else {
+    else {
         NS_WARNING("Unknown format specified to gfxImageSurface!");
         stride = aSize.width * 4;
     }
 
     stride = ((stride + 3) / 4) * 4;
 
     return stride;
 }
--- a/gfx/thebes/gfxQPainterSurface.cpp
+++ b/gfx/thebes/gfxQPainterSurface.cpp
@@ -16,19 +16,19 @@ gfxQPainterSurface::gfxQPainterSurface(Q
 
     mPainter = painter;
 
     Init (csurf);
 }
 
 gfxQPainterSurface::gfxQPainterSurface(const mozilla::gfx::IntSize& size, gfxImageFormat format)
 {
-    cairo_surface_t *csurf = cairo_qt_surface_create_with_qimage ((cairo_format_t) format,
-                                                                        size.width,
-                                                                        size.height);
+    cairo_format_t cformat = gfxImageFormatToCairoFormat(format);
+    cairo_surface_t *csurf =
+        cairo_qt_surface_create_with_qimage(cformat, size.width, size.height);
     mPainter = cairo_qt_surface_get_qpainter (csurf);
 
     Init (csurf);
 }
 
 gfxQPainterSurface::gfxQPainterSurface(const mozilla::gfx::IntSize& size, gfxContentType content)
 {
     cairo_surface_t *csurf = cairo_qt_surface_create_with_qpixmap ((cairo_content_t) content,
--- a/gfx/thebes/gfxQuartzSurface.cpp
+++ b/gfx/thebes/gfxQuartzSurface.cpp
@@ -21,18 +21,18 @@ gfxQuartzSurface::gfxQuartzSurface(const
     mozilla::gfx::IntSize size((unsigned int) floor(desiredSize.width),
                     (unsigned int) floor(desiredSize.height));
     if (!CheckSurfaceSize(size))
         MakeInvalid();
 
     unsigned int width = static_cast<unsigned int>(mSize.width);
     unsigned int height = static_cast<unsigned int>(mSize.height);
 
-    cairo_surface_t *surf = cairo_quartz_surface_create
-        ((cairo_format_t) format, width, height);
+    cairo_format_t cformat = gfxImageFormatToCairoFormat(format);
+    cairo_surface_t *surf = cairo_quartz_surface_create(cformat, width, height);
 
     mCGContext = cairo_quartz_surface_get_cg_context (surf);
 
     CGContextRetain(mCGContext);
 
     Init(surf);
     if (mSurfaceValid) {
       RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface));
@@ -104,18 +104,19 @@ gfxQuartzSurface::gfxQuartzSurface(unsig
     mozilla::gfx::IntSize size((unsigned int) floor(desiredSize.width),
                     (unsigned int) floor(desiredSize.height));
     if (!CheckSurfaceSize(size))
         MakeInvalid();
 
     unsigned int width = static_cast<unsigned int>(mSize.width);
     unsigned int height = static_cast<unsigned int>(mSize.height);
 
+    cairo_format_t cformat = gfxImageFormatToCairoFormat(format);
     cairo_surface_t *surf = cairo_quartz_surface_create_for_data
-        (data, (cairo_format_t) format, width, height, stride);
+        (data, cformat, width, height, stride);
 
     mCGContext = cairo_quartz_surface_get_cg_context (surf);
 
     CGContextRetain(mCGContext);
 
     Init(surf);
     if (mSurfaceValid) {
       RecordMemoryUsed(mSize.height * stride + sizeof(gfxQuartzSurface));
@@ -126,18 +127,19 @@ gfxQuartzSurface::gfxQuartzSurface(unsig
                                    const mozilla::gfx::IntSize& aSize,
                                    long stride,
                                    gfxImageFormat format)
     : mCGContext(nullptr), mSize(aSize.width, aSize.height)
 {
     if (!CheckSurfaceSize(aSize))
         MakeInvalid();
 
+    cairo_format_t cformat = gfxImageFormatToCairoFormat(format);
     cairo_surface_t *surf = cairo_quartz_surface_create_for_data
-        (data, (cairo_format_t) format, aSize.width, aSize.height, stride);
+        (data, cformat, aSize.width, aSize.height, stride);
 
     mCGContext = cairo_quartz_surface_get_cg_context (surf);
 
     CGContextRetain(mCGContext);
 
     Init(surf);
     if (mSurfaceValid) {
       RecordMemoryUsed(mSize.height * stride + sizeof(gfxQuartzSurface));
--- a/gfx/thebes/gfxTypes.h
+++ b/gfx/thebes/gfxTypes.h
@@ -47,21 +47,32 @@ enum class gfxBreakPriority {
 /**
   * The format for an image surface. For all formats with alpha data, 0
   * means transparent, 1 or 255 means fully opaque.
   */
 enum class gfxImageFormat {
   ARGB32, ///< ARGB data in native endianness, using premultiplied alpha
   RGB24,  ///< xRGB data in native endianness
   A8,     ///< Only an alpha channel
-  A1,     ///< Packed transparency information (one byte refers to 8 pixels)
   RGB16_565,  ///< RGB_565 data in native endianness
   Unknown
 };
 
+// XXX: temporary
+// This works because the gfxImageFormat enum is defined so as to match the
+// _cairo_format enum.
+#define gfxCairoFormatToImageFormat(aFormat) \
+    ((gfxImageFormat)aFormat)
+
+// XXX: temporary
+// This works because the gfxImageFormat enum is defined so as to match the
+// _cairo_format enum.
+#define gfxImageFormatToCairoFormat(aFormat) \
+    ((cairo_format_t)aFormat)
+
 enum class gfxSurfaceType {
   Image,
   PDF,
   PS,
   Xlib,
   Xcb,
   Glitz,           // unused, but needed for cairo parity
   Quartz,
--- a/gfx/thebes/gfxWindowsSurface.cpp
+++ b/gfx/thebes/gfxWindowsSurface.cpp
@@ -54,18 +54,19 @@ gfxWindowsSurface::MakeInvalid(mozilla::
 
 gfxWindowsSurface::gfxWindowsSurface(const mozilla::gfx::IntSize& realSize, gfxImageFormat imageFormat) :
     mOwnsDC(false), mForPrinting(false), mWnd(nullptr)
 {
     mozilla::gfx::IntSize size(realSize);
     if (!CheckSurfaceSize(size))
         MakeInvalid(size);
 
-    cairo_surface_t *surf = cairo_win32_surface_create_with_dib((cairo_format_t)(int)imageFormat,
-                                                                size.width, size.height);
+    cairo_format_t cformat = gfxImageFormatToCairoFormat(imageFormat);
+    cairo_surface_t *surf =
+        cairo_win32_surface_create_with_dib(cformat, size.width, size.height);
 
     Init(surf);
 
     if (CairoStatus() == CAIRO_STATUS_SUCCESS) {
         mDC = cairo_win32_surface_get_dc(CairoSurface());
         RecordMemoryUsed(size.width * size.height * 4 + sizeof(gfxWindowsSurface));
     } else {
         mDC = nullptr;
@@ -74,18 +75,20 @@ gfxWindowsSurface::gfxWindowsSurface(con
 
 gfxWindowsSurface::gfxWindowsSurface(HDC dc, const mozilla::gfx::IntSize& realSize, gfxImageFormat imageFormat) :
     mOwnsDC(false), mForPrinting(false), mWnd(nullptr)
 {
     mozilla::gfx::IntSize size(realSize);
     if (!CheckSurfaceSize(size))
         MakeInvalid(size);
 
-    cairo_surface_t *surf = cairo_win32_surface_create_with_ddb(dc, (cairo_format_t)(int)imageFormat,
-                                                                size.width, size.height);
+    cairo_format_t cformat = gfxImageFormatToCairoFormat(imageFormat);
+    cairo_surface_t *surf =
+        cairo_win32_surface_create_with_ddb(dc, cformat,
+                                            size.width, size.height);
 
     Init(surf);
 
     if (mSurfaceValid) {
         // DDBs will generally only use 3 bytes per pixel when RGB24
         int bytesPerPixel = ((imageFormat == gfxImageFormat::RGB24) ? 3 : 4);
         RecordMemoryUsed(size.width * size.height * bytesPerPixel + sizeof(gfxWindowsSurface));
     }
@@ -133,19 +136,21 @@ gfxWindowsSurface::CreateSimilarSurface(
         // When creating a similar surface to a transparent surface, ensure
         // the new surface uses a DIB. cairo_surface_create_similar won't
         // use  a DIB for a gfxContentType::COLOR surface if this surface doesn't
         // have a DIB (e.g. if we're a transparent window surface). But
         // we need a DIB to perform well if the new surface is composited into
         // a surface that's the result of create_similar(gfxContentType::COLOR_ALPHA)
         // (e.g. a backbuffer for the window) --- that new surface *would*
         // have a DIB.
-        surface =
-          cairo_win32_surface_create_with_dib((cairo_format_t)(int)gfxPlatform::GetPlatform()->OptimalFormatForContent(aContent),
-                                              aSize.width, aSize.height);
+        gfxImageFormat gformat =
+            gfxPlatform::GetPlatform()->OptimalFormatForContent(aContent);
+        cairo_format_t cformat = gfxImageFormatToCairoFormat(gformat);
+        surface = cairo_win32_surface_create_with_dib(cformat, aSize.width,
+                                                      aSize.height);
     } else {
         surface =
           cairo_surface_create_similar(mSurface, (cairo_content_t)(int)aContent,
                                        aSize.width, aSize.height);
     }
 
     if (cairo_surface_status(surface)) {
         cairo_surface_destroy(surface);
--- a/gfx/thebes/gfxXlibNativeRenderer.cpp
+++ b/gfx/thebes/gfxXlibNativeRenderer.cpp
@@ -347,17 +347,17 @@ CreateTempXlibSurface (cairo_surface_t* 
         Visual *target_visual = nullptr;
         XRenderPictFormat *target_format = nullptr;
         if (cairoTargetType == CAIRO_SURFACE_TYPE_XLIB) {
             target_visual = cairo_xlib_surface_get_visual (cairoTarget);
             target_format = cairo_xlib_surface_get_xrender_format (cairoTarget);
         } else if (cairoTargetType == CAIRO_SURFACE_TYPE_IMAGE || drawTarget) {
             gfxImageFormat imageFormat =
                 drawTarget ? SurfaceFormatToImageFormat(drawTarget->GetFormat()) :
-                    (gfxImageFormat)cairo_image_surface_get_format(cairoTarget);
+                    gfxCairoFormatToImageFormat(cairo_image_surface_get_format(cairoTarget));
             target_visual = gfxXlibSurface::FindVisual(screen, imageFormat);
             Display *dpy = DisplayOfScreen(screen);
             if (target_visual) {
                 target_format = XRenderFindVisualFormat(dpy, target_visual);
             } else {
                 target_format =
                     gfxXlibSurface::FindRenderFormat(dpy, imageFormat);
             }
--- a/gfx/thebes/gfxXlibSurface.cpp
+++ b/gfx/thebes/gfxXlibSurface.cpp
@@ -518,17 +518,16 @@ gfxXlibSurface::FindVisual(Screen *scree
             break;
         case gfxImageFormat::RGB16_565:
             depth = 16;
             red_mask = 0xf800;
             green_mask = 0x7e0;
             blue_mask = 0x1f;
             break;
         case gfxImageFormat::A8:
-        case gfxImageFormat::A1:
         default:
             return nullptr;
     }
 
     for (int d = 0; d < screen->ndepths; d++) {
         const Depth& d_info = screen->depths[d];
         if (d_info.depth != depth)
             continue;
@@ -562,18 +561,16 @@ gfxXlibSurface::FindRenderFormat(Display
             // and find xrender format by visual
             Visual *visual = FindVisual(DefaultScreenOfDisplay(dpy), format);
             if (!visual)
                 return nullptr;
             return XRenderFindVisualFormat(dpy, visual);
         }
         case gfxImageFormat::A8:
             return XRenderFindStandardFormat (dpy, PictStandardA8);
-        case gfxImageFormat::A1:
-            return XRenderFindStandardFormat (dpy, PictStandardA1);
         default:
             break;
     }
 
     return nullptr;
 }
 
 Screen*
--- a/hal/HalWakeLock.cpp
+++ b/hal/HalWakeLock.cpp
@@ -1,9 +1,10 @@
-/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Hal.h"
 #include "mozilla/HalWakeLock.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
@@ -49,56 +50,31 @@ WakeLockInfoFromLockCount(const nsAStrin
   WakeLockInformation info;
   info.topic() = aTopic;
   info.numLocks() = aLockCount.numLocks;
   info.numHidden() = aLockCount.numHidden;
   info.lockingProcesses().AppendElements(aLockCount.processes);
   return info;
 }
 
-PLDHashOperator
-CountWakeLocks(const uint64_t& aKey, LockCount aCount, void* aUserArg)
+static void
+CountWakeLocks(ProcessLockTable* aTable, LockCount* aTotalCount)
 {
-  MOZ_ASSERT(aUserArg);
-
-  LockCount* totalCount = static_cast<LockCount*>(aUserArg);
-  totalCount->numLocks += aCount.numLocks;
-  totalCount->numHidden += aCount.numHidden;
-
-  // This is linear in the number of processes, but that should be small.
-  if (!totalCount->processes.Contains(aKey)) {
-    totalCount->processes.AppendElement(aKey);
-  }
-
-  return PL_DHASH_NEXT;
-}
+  for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
+    const uint64_t& key = iter.Key();
+    LockCount count = iter.UserData();
 
-static PLDHashOperator
-RemoveChildFromList(const nsAString& aKey, nsAutoPtr<ProcessLockTable>& aTable,
-                    void* aUserArg)
-{
-  MOZ_ASSERT(aUserArg);
+    aTotalCount->numLocks += count.numLocks;
+    aTotalCount->numHidden += count.numHidden;
 
-  PLDHashOperator op = PL_DHASH_NEXT;
-  uint64_t childID = *static_cast<uint64_t*>(aUserArg);
-  if (aTable->Get(childID, nullptr)) {
-    aTable->Remove(childID);
-
-    LockCount totalCount;
-    aTable->EnumerateRead(CountWakeLocks, &totalCount);
-    if (!totalCount.numLocks) {
-      op = PL_DHASH_REMOVE;
-    }
-
-    if (sActiveListeners) {
-      NotifyWakeLockChange(WakeLockInfoFromLockCount(aKey, totalCount));
+    // This is linear in the number of processes, but that should be small.
+    if (!aTotalCount->processes.Contains(key)) {
+      aTotalCount->processes.AppendElement(key);
     }
   }
-
-  return op;
 }
 
 class ClearHashtableOnShutdown final : public nsIObserver {
   ~ClearHashtableOnShutdown() {}
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 };
@@ -139,17 +115,35 @@ CleanupOnContentShutdown::Observe(nsISup
     NS_WARNING("ipc:content-shutdown message without property bag as subject");
     return NS_OK;
   }
 
   uint64_t childID = 0;
   nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"),
                                            &childID);
   if (NS_SUCCEEDED(rv)) {
-    sLockTable->Enumerate(RemoveChildFromList, &childID);
+    for (auto iter = sLockTable->Iter(); !iter.Done(); iter.Next()) {
+      nsAutoPtr<ProcessLockTable>& table = iter.Data();
+
+      if (table->Get(childID, nullptr)) {
+        table->Remove(childID);
+
+        LockCount totalCount;
+        CountWakeLocks(table, &totalCount);
+
+        if (sActiveListeners) {
+          NotifyWakeLockChange(WakeLockInfoFromLockCount(iter.Key(),
+                                                         totalCount));
+        }
+
+        if (totalCount.numLocks == 0) {
+          iter.Remove();
+        }
+      }
+    }
   } else {
     NS_WARNING("ipc:content-shutdown message without childID property");
   }
   return NS_OK;
 }
 
 void
 Init()
@@ -217,17 +211,17 @@ ModifyWakeLock(const nsAString& aTopic,
   ProcessLockTable* table = sLockTable->Get(aTopic);
   LockCount processCount;
   LockCount totalCount;
   if (!table) {
     table = new ProcessLockTable();
     sLockTable->Put(aTopic, table);
   } else {
     table->Get(aProcessID, &processCount);
-    table->EnumerateRead(CountWakeLocks, &totalCount);
+    CountWakeLocks(table, &totalCount);
   }
 
   MOZ_ASSERT(processCount.numLocks >= processCount.numHidden);
   MOZ_ASSERT(aLockAdjust >= 0 || processCount.numLocks > 0);
   MOZ_ASSERT(aHiddenAdjust >= 0 || processCount.numHidden > 0);
   MOZ_ASSERT(totalCount.numLocks >= totalCount.numHidden);
   MOZ_ASSERT(aLockAdjust >= 0 || totalCount.numLocks > 0);
   MOZ_ASSERT(aHiddenAdjust >= 0 || totalCount.numHidden > 0);
@@ -274,14 +268,14 @@ GetWakeLockInfo(const nsAString& aTopic,
   }
 
   ProcessLockTable* table = sLockTable->Get(aTopic);
   if (!table) {
     *aWakeLockInfo = WakeLockInfoFromLockCount(aTopic, LockCount());
     return;
   }
   LockCount totalCount;
-  table->EnumerateRead(CountWakeLocks, &totalCount);
+  CountWakeLocks(table, &totalCount);
   *aWakeLockInfo = WakeLockInfoFromLockCount(aTopic, totalCount);
 }
 
 } // namespace hal_impl
 } // namespace mozilla
--- a/js/src/builtin/WeakSet.js
+++ b/js/src/builtin/WeakSet.js
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // 23.4.3.1
 function WeakSet_add(value) {
     // Steps 1-3.
     var S = this;
     if (!IsObject(S) || !IsWeakSet(S))
-        ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "add", typeof S);
+        return callFunction(CallWeakSetMethodIfWrapped, this, value, "WeakSet_add");
 
     // Step 4.,6.
     let entries = UnsafeGetReservedSlot(this, WEAKSET_MAP_SLOT);
     if (!entries)
         ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "add", typeof S);
 
     // Step 5.
     if (!IsObject(value))
@@ -25,17 +25,17 @@ function WeakSet_add(value) {
     return S;
 }
 
 // 23.4.3.2
 function WeakSet_clear() {
     // Step 1-3.
     var S = this;
     if (!IsObject(S) || !IsWeakSet(S))
-        ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "clear", typeof S);
+        return callFunction(CallWeakSetMethodIfWrapped, this, "WeakSet_clear");
 
     // Step 4.
     let entries = UnsafeGetReservedSlot(this, WEAKSET_MAP_SLOT);
     if (!entries)
         ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "clear", typeof S);
 
     // Step 5.
     callFunction(std_WeakMap_clear, entries);
@@ -44,17 +44,17 @@ function WeakSet_clear() {
     return undefined;
 }
 
 // 23.4.3.4
 function WeakSet_delete(value) {
     // Steps 1-3.
     var S = this;
     if (!IsObject(S) || !IsWeakSet(S))
-        ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "delete", typeof S);
+        return callFunction(CallWeakSetMethodIfWrapped, this, value, "WeakSet_delete");
 
     // Step 4.,6.
     let entries = UnsafeGetReservedSlot(this, WEAKSET_MAP_SLOT);
     if (!entries)
         ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "delete", typeof S);
 
     // Step 5.
     if (!IsObject(value))
@@ -64,17 +64,17 @@ function WeakSet_delete(value) {
     return callFunction(std_WeakMap_delete, entries, value);
 }
 
 // 23.4.3.5
 function WeakSet_has(value) {
     // Steps 1-3.
     var S = this;
     if (!IsObject(S) || !IsWeakSet(S))
-        ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "has", typeof S);
+        return callFunction(CallWeakSetMethodIfWrapped, this, value, "WeakSet_has");
 
     // Step 4-5.
     let entries = UnsafeGetReservedSlot(this, WEAKSET_MAP_SLOT);
     if (!entries)
         ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "has", typeof S);
 
     // Step 6.
     if (!IsObject(value))
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -816,18 +816,18 @@ struct DefaultHasher<PreBarriered<T>> : 
 /* Useful for hashtables with a ReadBarriered as key. */
 template <class T>
 struct ReadBarrieredHasher
 {
     typedef ReadBarriered<T> Key;
     typedef T Lookup;
 
     static HashNumber hash(Lookup obj) { return DefaultHasher<T>::hash(obj); }
-    static bool match(const Key& k, Lookup l) { return k.get() == l; }
-    static void rekey(Key& k, const Key& newKey) { k.set(newKey); }
+    static bool match(const Key& k, Lookup l) { return k.unbarrieredGet() == l; }
+    static void rekey(Key& k, const Key& newKey) { k.set(newKey.unbarrieredGet()); }
 };
 
 /* Specialized hashing policy for ReadBarriereds. */
 template <class T>
 struct DefaultHasher<ReadBarriered<T>> : ReadBarrieredHasher<T> { };
 
 class ArrayObject;
 class ArrayBufferObject;
@@ -889,24 +889,21 @@ typedef PreBarriered<jsid> PreBarrieredI
 typedef RelocatablePtr<jsid> RelocatableId;
 typedef HeapPtr<jsid> HeapId;
 
 typedef ImmutableTenuredPtr<PropertyName*> ImmutablePropertyNamePtr;
 typedef ImmutableTenuredPtr<JS::Symbol*> ImmutableSymbolPtr;
 
 typedef ReadBarriered<DebugScopeObject*> ReadBarrieredDebugScopeObject;
 typedef ReadBarriered<GlobalObject*> ReadBarrieredGlobalObject;
-typedef ReadBarriered<JSFunction*> ReadBarrieredFunction;
 typedef ReadBarriered<JSObject*> ReadBarrieredObject;
 typedef ReadBarriered<JSScript*> ReadBarrieredScript;
 typedef ReadBarriered<ScriptSourceObject*> ReadBarrieredScriptSourceObject;
 typedef ReadBarriered<Shape*> ReadBarrieredShape;
-typedef ReadBarriered<UnownedBaseShape*> ReadBarrieredUnownedBaseShape;
 typedef ReadBarriered<jit::JitCode*> ReadBarrieredJitCode;
 typedef ReadBarriered<ObjectGroup*> ReadBarrieredObjectGroup;
-typedef ReadBarriered<JSAtom*> ReadBarrieredAtom;
 typedef ReadBarriered<JS::Symbol*> ReadBarrieredSymbol;
 
 typedef ReadBarriered<Value> ReadBarrieredValue;
 
 } /* namespace js */
 
 #endif /* gc_Barrier_h */
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -659,16 +659,17 @@ class GCRuntime
         MOZ_ASSERT(nextCellUniqueId_ > 0);
         return nextCellUniqueId_++;
     }
 
   public:
     // Internal public interface
     js::gc::State state() const { return incrementalState; }
     bool isHeapCompacting() const { return state() == COMPACT; }
+    bool isForegroundSweeping() const { return state() == SWEEP; }
     bool isBackgroundSweeping() { return helperState.isBackgroundSweeping(); }
     void waitBackgroundSweepEnd() { helperState.waitBackgroundSweepEnd(); }
     void waitBackgroundSweepOrAllocEnd() {
         helperState.waitBackgroundSweepEnd();
         allocTask.cancel(GCParallelTask::CancelAndWait);
     }
 
     void requestMinorGC(JS::gcreason::Reason reason);
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -1452,18 +1452,23 @@ TenuredCell::isInsideZone(JS::Zone* zone
 
 /* static */ MOZ_ALWAYS_INLINE void
 TenuredCell::readBarrier(TenuredCell* thing)
 {
     MOZ_ASSERT(!CurrentThreadIsIonCompiling());
     MOZ_ASSERT(!isNullLike(thing));
     if (thing->shadowRuntimeFromAnyThread()->isHeapBusy())
         return;
+    MOZ_ASSERT_IF(CurrentThreadCanAccessRuntime(thing->runtimeFromAnyThread()),
+                  !thing->shadowRuntimeFromAnyThread()->isHeapCollecting());
 
     JS::shadow::Zone* shadowZone = thing->shadowZoneFromAnyThread();
+    MOZ_ASSERT_IF(!CurrentThreadCanAccessRuntime(thing->runtimeFromAnyThread()),
+                  !shadowZone->needsIncrementalBarrier());
+
     if (shadowZone->needsIncrementalBarrier()) {
         MOZ_ASSERT(!RuntimeFromMainThreadIsHeapMajorCollecting(shadowZone));
         Cell* tmp = thing;
         TraceManuallyBarrieredGenericPointerEdge(shadowZone->barrierTracer(), &tmp, "read barrier");
         MOZ_ASSERT(tmp == thing);
     }
     if (thing->isMarked(GRAY))
         UnmarkGrayCellRecursively(thing, thing->getTraceKind());
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2441,21 +2441,21 @@ TypeSet::MarkTypeRoot(JSTracer* trc, Typ
     AssertRootMarkingPhase(trc);
     MarkTypeUnbarriered(trc, v, name);
 }
 
 void
 TypeSet::MarkTypeUnbarriered(JSTracer* trc, TypeSet::Type* v, const char* name)
 {
     if (v->isSingletonUnchecked()) {
-        JSObject* obj = v->singleton();
+        JSObject* obj = v->singletonNoBarrier();
         DispatchToTracer(trc, &obj, name);
         *v = TypeSet::ObjectType(obj);
     } else if (v->isGroupUnchecked()) {
-        ObjectGroup* group = v->group();
+        ObjectGroup* group = v->groupNoBarrier();
         DispatchToTracer(trc, &group, name);
         *v = TypeSet::ObjectType(group);
     }
 }
 
 
 /*** Cycle Collector Barrier Implementation *******************************************************/
 
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -142,17 +142,17 @@ Zone::logPromotionsToTenured()
     auto now = JS_GetCurrentEmbedderTime();
     JSRuntime* rt = runtimeFromAnyThread();
 
     for (auto** dbgp = dbgs->begin(); dbgp != dbgs->end(); dbgp++) {
         if (!(*dbgp)->isEnabled() || !(*dbgp)->isTrackingTenurePromotions())
             continue;
 
         for (auto range = awaitingTenureLogging.all(); !range.empty(); range.popFront()) {
-            if ((*dbgp)->isDebuggee(range.front()->compartment()))
+            if ((*dbgp)->isDebuggeeUnbarriered(range.front()->compartment()))
                 (*dbgp)->logTenurePromotion(rt, *range.front(), now);
         }
     }
 
     awaitingTenureLogging.clear();
 }
 
 void
@@ -306,17 +306,17 @@ Zone::canCollect()
     return true;
 }
 
 void
 Zone::notifyObservingDebuggers()
 {
     for (CompartmentsInZoneIter comps(this); !comps.done(); comps.next()) {
         JSRuntime* rt = runtimeFromAnyThread();
-        RootedGlobalObject global(rt, comps->maybeGlobal());
+        RootedGlobalObject global(rt, comps->unsafeUnbarrieredMaybeGlobal());
         if (!global)
             continue;
 
         GlobalObject::DebuggerVector* dbgs = global->getDebuggers();
         if (!dbgs)
             continue;
 
         for (GlobalObject::DebuggerVector::Range r = dbgs->all(); !r.empty(); r.popFront()) {
--- a/js/src/jit-test/tests/collections/WeakSet-clear.js
+++ b/js/src/jit-test/tests/collections/WeakSet-clear.js
@@ -35,8 +35,16 @@ ws.add(value);
 value = null;
 ws.clear();
 gc();
 var value2 = {};
 ws.add(value2);
 value2 = null;
 gc();
 ws.clear();
+
+// Clearing a cross-compartment WeakSet with a live value
+ws = new (newGlobal().WeakSet);
+value = {};
+WeakSet.prototype.add.call(ws, value);
+assertEq(WeakSet.prototype.has.call(ws, value), true);
+WeakSet.prototype.clear.call(ws);
+assertEq(WeakSet.prototype.has.call(ws, value), false);
--- a/js/src/jit-test/tests/collections/WeakSet-delete.js
+++ b/js/src/jit-test/tests/collections/WeakSet-delete.js
@@ -24,8 +24,16 @@ assertEq(ws.add(value), ws);
 assertEq(ws.has(value), true);
 assertEq(ws.delete(value), true);
 assertEq(ws.has(value), false);
 assertEq(ws.delete(value), false);
 assertEq(ws.has(value), false);
 
 // Delete primitive
 assertEq(ws.delete(15), false);
+
+// Delete with cross-compartment WeakSet
+ws = new (newGlobal().WeakSet);
+WeakSet.prototype.add.call(ws, value);
+assertEq(WeakSet.prototype.has.call(ws, value), true);
+assertEq(WeakSet.prototype.delete.call(ws, value), true);
+assertEq(WeakSet.prototype.has.call(ws, value), false);
+assertEq(WeakSet.prototype.delete.call(ws, value), false);
--- a/js/src/jsapi-tests/testSavedStacks.cpp
+++ b/js/src/jsapi-tests/testSavedStacks.cpp
@@ -106,8 +106,99 @@ BEGIN_TEST(testSavedStacks_RangeBasedFor
         CHECK(frame == rf);
         rf = rf->getParent();
     }
     CHECK(rf == nullptr);
 
     return true;
 }
 END_TEST(testSavedStacks_RangeBasedForLoops)
+
+BEGIN_TEST(testSavedStacks_selfHostedFrames)
+{
+    CHECK(js::DefineTestingFunctions(cx, global, false, false));
+
+    JS::RootedValue val(cx);
+    //             0         1         2         3
+    //             0123456789012345678901234567890123456789
+    CHECK(evaluate("(function one() {                      \n"  // 1
+                   "  try {                                \n"  // 2
+                   "    [1].map(function two() {           \n"  // 3
+                   "      throw saveStack();               \n"  // 4
+                   "    });                                \n"  // 5
+                   "  } catch (stack) {                    \n"  // 6
+                   "    return stack;                      \n"  // 7
+                   "  }                                    \n"  // 8
+                   "}())                                   \n", // 9
+                   "filename.js",
+                   1,
+                   &val));
+
+    CHECK(val.isObject());
+    JS::RootedObject obj(cx, &val.toObject());
+
+    CHECK(obj->is<js::SavedFrame>());
+    JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>());
+
+    JS::Rooted<js::SavedFrame*> selfHostedFrame(cx, savedFrame->getParent());
+    CHECK(selfHostedFrame->isSelfHosted());
+
+    // Source
+    JS::RootedString str(cx);
+    JS::SavedFrameResult result = JS::GetSavedFrameSource(cx, selfHostedFrame, &str,
+                                                          JS::SavedFrameSelfHosted::Exclude);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    JSLinearString* lin = str->ensureLinear(cx);
+    CHECK(lin);
+    CHECK(js::StringEqualsAscii(lin, "filename.js"));
+
+    // Source, including self-hosted frames
+    result = JS::GetSavedFrameSource(cx, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Include);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    lin = str->ensureLinear(cx);
+    CHECK(lin);
+    CHECK(js::StringEqualsAscii(lin, "self-hosted"));
+
+    // Line
+    uint32_t line = 123;
+    result = JS::GetSavedFrameLine(cx, selfHostedFrame, &line, JS::SavedFrameSelfHosted::Exclude);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    CHECK_EQUAL(line, 3U);
+
+    // Column
+    uint32_t column = 123;
+    result = JS::GetSavedFrameColumn(cx, selfHostedFrame, &column,
+                                     JS::SavedFrameSelfHosted::Exclude);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    CHECK_EQUAL(column, 5U);
+
+    // Function display name
+    result = JS::GetSavedFrameFunctionDisplayName(cx, selfHostedFrame, &str,
+                                                  JS::SavedFrameSelfHosted::Exclude);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    lin = str->ensureLinear(cx);
+    CHECK(lin);
+    CHECK(js::StringEqualsAscii(lin, "one"));
+
+    // Parent
+    JS::RootedObject parent(cx);
+    result = JS::GetSavedFrameParent(cx, savedFrame, &parent, JS::SavedFrameSelfHosted::Exclude);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    // JS::GetSavedFrameParent does this super funky and potentially unexpected
+    // thing where it doesn't return the next subsumed parent but any next
+    // parent. This so that callers can still get the "asyncParent" property
+    // which is only on the first frame of the async parent stack and that frame
+    // might not be subsumed by the caller. It is expected that callers will
+    // still interact with the frame through the JSAPI accessors, so this should
+    // be safe and should not leak privileged info to unprivileged
+    // callers. However, because of that, we don't test that the parent we get
+    // here is the selfHostedFrame's parent (because, as just explained, it
+    // isn't) and instead check that asking for the source property gives us the
+    // expected value.
+    result = JS::GetSavedFrameSource(cx, parent, &str, JS::SavedFrameSelfHosted::Exclude);
+    CHECK(result == JS::SavedFrameResult::Ok);
+    lin = str->ensureLinear(cx);
+    CHECK(lin);
+    CHECK(js::StringEqualsAscii(lin, "filename.js"));
+
+    return true;
+}
+END_TEST(testSavedStacks_selfHostedFrames)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5545,79 +5545,95 @@ CaptureCurrentStack(JSContext* cx, Mutab
  * Each of these functions will find the first SavedFrame object in the chain
  * whose underlying stack frame principals are subsumed by the cx's current
  * compartment's principals, and operate on that SavedFrame object. This
  * prevents leaking information about privileged frames to un-privileged
  * callers. As a result, the SavedFrame in parameters do _NOT_ need to be in the
  * same compartment as the cx, and the various out parameters are _NOT_
  * guaranteed to be in the same compartment as cx.
  *
+ * You may consider or skip over self-hosted frames by passing
+ * `SavedFrameSelfHosted::Include` or `SavedFrameSelfHosted::Exclude`
+ * respectively.
+ *
  * Additionally, it may be the case that there is no such SavedFrame object
  * whose captured frame's principals are subsumed by the caller's compartment's
  * principals! If the `HandleObject savedFrame` argument is null, or the
  * caller's principals do not subsume any of the chained SavedFrame object's
  * principals, `SavedFrameResult::AccessDenied` is returned and a (hopefully)
  * sane default value is chosen for the out param.
  *
  * See also `js/src/doc/SavedFrame/SavedFrame.md`.
  */
 
 enum class SavedFrameResult {
     Ok,
     AccessDenied
 };
 
+enum class SavedFrameSelfHosted {
+    Include,
+    Exclude
+};
+
 /**
  * Given a SavedFrame JSObject, get its source property. Defaults to the empty
  * string.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameSource(JSContext* cx, HandleObject savedFrame, MutableHandleString sourcep);
+GetSavedFrameSource(JSContext* cx, HandleObject savedFrame, MutableHandleString sourcep,
+                    SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject, get its line property. Defaults to 0.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameLine(JSContext* cx, HandleObject savedFrame, uint32_t* linep);
+GetSavedFrameLine(JSContext* cx, HandleObject savedFrame, uint32_t* linep,
+                  SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject, get its column property. Defaults to 0.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameColumn(JSContext* cx, HandleObject savedFrame, uint32_t* columnp);
+GetSavedFrameColumn(JSContext* cx, HandleObject savedFrame, uint32_t* columnp,
+                    SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject, get its functionDisplayName string, or nullptr
  * if SpiderMonkey was unable to infer a name for the captured frame's
  * function. Defaults to nullptr.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameFunctionDisplayName(JSContext* cx, HandleObject savedFrame, MutableHandleString namep);
+GetSavedFrameFunctionDisplayName(JSContext* cx, HandleObject savedFrame, MutableHandleString namep,
+                                 SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject, get its asyncCause string. Defaults to nullptr.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameAsyncCause(JSContext* cx, HandleObject savedFrame, MutableHandleString asyncCausep);
+GetSavedFrameAsyncCause(JSContext* cx, HandleObject savedFrame, MutableHandleString asyncCausep,
+                        SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject, get its asyncParent SavedFrame object or nullptr
  * if there is no asyncParent. The `asyncParentp` out parameter is _NOT_
  * guaranteed to be in the cx's compartment. Defaults to nullptr.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameAsyncParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject asyncParentp);
+GetSavedFrameAsyncParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject asyncParentp,
+                SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject, get its parent SavedFrame object or nullptr if
  * it is the oldest frame in the stack. The `parentp` out parameter is _NOT_
  * guaranteed to be in the cx's compartment. Defaults to nullptr.
  */
 extern JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject parentp);
+GetSavedFrameParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject parentp,
+                    SavedFrameSelfHosted selfHosted = SavedFrameSelfHosted::Include);
 
 /**
  * Given a SavedFrame JSObject stack, stringify it in the same format as
  * Error.prototype.stack. The stringified stack out parameter is placed in the
  * cx's compartment. Defaults to the empty string.
  *
  * The same notes above about SavedFrame accessors applies here as well: cx
  * doesn't need to be in stack's compartment, and stack can be null, a
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -194,21 +194,19 @@ void
 js::MarkAtoms(JSTracer* trc)
 {
     JSRuntime* rt = trc->runtime();
     for (AtomSet::Enum e(rt->atoms()); !e.empty(); e.popFront()) {
         const AtomStateEntry& entry = e.front();
         if (!entry.isPinned())
             continue;
 
-        JSAtom* atom = entry.asPtr();
-        bool tagged = entry.isPinned();
+        JSAtom* atom = entry.asPtrUnbarriered();
         TraceRoot(trc, &atom, "interned_atom");
-        if (entry.asPtr() != atom)
-            e.rekeyFront(AtomHasher::Lookup(atom), AtomStateEntry(atom, tagged));
+        MOZ_ASSERT(entry.asPtrUnbarriered() == atom);
     }
 }
 
 void
 js::MarkPermanentAtoms(JSTracer* trc)
 {
     JSRuntime* rt = trc->runtime();
 
@@ -247,17 +245,17 @@ js::MarkWellKnownSymbols(JSTracer* trc)
 void
 JSRuntime::sweepAtoms()
 {
     if (!atoms_)
         return;
 
     for (AtomSet::Enum e(*atoms_); !e.empty(); e.popFront()) {
         AtomStateEntry entry = e.front();
-        JSAtom* atom = entry.asPtr();
+        JSAtom* atom = entry.asPtrUnbarriered();
         bool isDying = IsAboutToBeFinalizedUnbarriered(&atom);
 
         /* Pinned or interned key cannot be finalized. */
         MOZ_ASSERT_IF(hasContexts() && entry.isPinned(), !isDying);
 
         if (isDying)
             e.removeFront();
     }
--- a/js/src/jsatom.h
+++ b/js/src/jsatom.h
@@ -70,16 +70,17 @@ class AtomStateEntry
      * Non-branching code sequence. Note that the const_cast is safe because
      * the hash function doesn't consider the tag to be a portion of the key.
      */
     void setPinned(bool pinned) const {
         const_cast<AtomStateEntry*>(this)->bits |= uintptr_t(pinned);
     }
 
     JSAtom* asPtr() const;
+    JSAtom* asPtrUnbarriered() const;
 };
 
 struct AtomHasher
 {
     struct Lookup
     {
         union {
             const JS::Latin1Char* latin1Chars;
--- a/js/src/jsatominlines.h
+++ b/js/src/jsatominlines.h
@@ -21,16 +21,23 @@ inline JSAtom*
 js::AtomStateEntry::asPtr() const
 {
     MOZ_ASSERT(bits != 0);
     JSAtom* atom = reinterpret_cast<JSAtom*>(bits & NO_TAG_MASK);
     JSString::readBarrier(atom);
     return atom;
 }
 
+inline JSAtom*
+js::AtomStateEntry::asPtrUnbarriered() const
+{
+    MOZ_ASSERT(bits != 0);
+    return reinterpret_cast<JSAtom*>(bits & NO_TAG_MASK);
+}
+
 namespace js {
 
 inline jsid
 AtomToId(JSAtom* atom)
 {
     JS_STATIC_ASSERT(JSID_INT_MIN == 0);
 
     uint32_t index;
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -259,17 +259,18 @@ JSCompartment::checkWrapperMapAfterMovin
      * Assert that the postbarriers have worked and that nothing is left in
      * wrapperMap that points into the nursery, and that the hash table entries
      * are discoverable.
      */
     for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
         CrossCompartmentKey key = e.front().key();
         CheckGCThingAfterMovingGC(key.debugger);
         CheckGCThingAfterMovingGC(key.wrapped);
-        CheckGCThingAfterMovingGC(static_cast<Cell*>(e.front().value().get().toGCThing()));
+        CheckGCThingAfterMovingGC(
+                static_cast<Cell*>(e.front().value().unbarrieredGet().toGCThing()));
 
         WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(key);
         MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front());
     }
 }
 #endif
 
 bool
@@ -540,17 +541,17 @@ JSCompartment::getNonSyntacticLexicalSco
 
 void
 JSCompartment::traceOutgoingCrossCompartmentWrappers(JSTracer* trc)
 {
     MOZ_ASSERT(trc->runtime()->isHeapMajorCollecting());
     MOZ_ASSERT(!zone()->isCollecting() || trc->runtime()->gc.isHeapCompacting());
 
     for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
-        Value v = e.front().value();
+        Value v = e.front().value().unbarrieredGet();
         if (e.front().key().kind == CrossCompartmentKey::ObjectWrapper) {
             ProxyObject* wrapper = &v.toObject().as<ProxyObject>();
 
             /*
              * We have a cross-compartment wrapper. Its private pointer may
              * point into the compartment being collected, so we should mark it.
              */
             TraceEdge(trc, wrapper->slotOfPrivate(), "cross-compartment wrapper");
@@ -667,19 +668,19 @@ void
 JSCompartment::sweepSavedStacks()
 {
     savedStacks_.sweep(runtimeFromAnyThread());
 }
 
 void
 JSCompartment::sweepGlobalObject(FreeOp* fop)
 {
-    if (global_.unbarrieredGet() && IsAboutToBeFinalized(&global_)) {
+    if (global_ && IsAboutToBeFinalized(&global_)) {
         if (isDebuggee())
-            Debugger::detachAllDebuggersFromGlobal(fop, global_);
+            Debugger::detachAllDebuggersFromGlobal(fop, global_.unbarrieredGet());
         global_.set(nullptr);
     }
 }
 
 void
 JSCompartment::sweepObjectPendingMetadata()
 {
     if (objectMetadataState.is<PendingMetadata>()) {
@@ -1006,17 +1007,20 @@ JSCompartment::ensureDelazifyScriptsForD
 void
 JSCompartment::updateDebuggerObservesFlag(unsigned flag)
 {
     MOZ_ASSERT(isDebuggee());
     MOZ_ASSERT(flag == DebuggerObservesAllExecution ||
                flag == DebuggerObservesCoverage ||
                flag == DebuggerObservesAsmJS);
 
-    const GlobalObject::DebuggerVector* v = maybeGlobal()->getDebuggers();
+    GlobalObject* global = zone()->runtimeFromMainThread()->gc.isForegroundSweeping()
+                           ? unsafeUnbarrieredMaybeGlobal()
+                           : maybeGlobal();
+    const GlobalObject::DebuggerVector* v = global->getDebuggers();
     for (Debugger * const* p = v->begin(); p != v->end(); p++) {
         Debugger* dbg = *p;
         if (flag == DebuggerObservesAllExecution ? dbg->observesAllExecution() :
             flag == DebuggerObservesCoverage ? dbg->observesCoverage() :
             dbg->observesAsmJS())
         {
             debugModeBits |= flag;
             return;
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2843,17 +2843,17 @@ GetNearestEnclosingWithScopeObjectForFun
  * Get the first SavedFrame object in this SavedFrame stack whose principals are
  * subsumed by the cx's principals. If there is no such frame, return nullptr.
  *
  * Do NOT pass a non-SavedFrame object here.
  *
  * The savedFrame and cx do not need to be in the same compartment.
  */
 extern JS_FRIEND_API(JSObject*)
-GetFirstSubsumedSavedFrame(JSContext* cx, JS::HandleObject savedFrame);
+GetFirstSubsumedSavedFrame(JSContext* cx, JS::HandleObject savedFrame, JS::SavedFrameSelfHosted selfHosted);
 
 extern JS_FRIEND_API(bool)
 ReportIsNotFunction(JSContext* cx, JS::HandleValue v);
 
 extern JS_FRIEND_API(JSObject*)
 ConvertArgsToArray(JSContext* cx, const JS::CallArgs& args);
 
 } /* namespace js */
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -5221,17 +5221,17 @@ GCRuntime::beginSweepPhase(bool destroyi
 
     releaseObservedTypes = shouldReleaseObservedTypes();
 
 #ifdef DEBUG
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         MOZ_ASSERT(!c->gcIncomingGrayPointers);
         for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
             if (e.front().key().kind != CrossCompartmentKey::StringWrapper)
-                AssertNotOnGrayList(&e.front().value().get().toObject());
+                AssertNotOnGrayList(&e.front().value().unbarrieredGet().toObject());
         }
     }
 #endif
 
     DropStringWrappers(rt);
 
     findZoneGroups();
     endMarkingZoneGroup();
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -1272,17 +1272,17 @@ CheckGCThingAfterMovingGC(T* t)
         MOZ_RELEASE_ASSERT(!RelocationOverlay::isCellForwarded(t));
     }
 }
 
 template <typename T>
 inline void
 CheckGCThingAfterMovingGC(const ReadBarriered<T*>& t)
 {
-    CheckGCThingAfterMovingGC(t.get());
+    CheckGCThingAfterMovingGC(t.unbarrieredGet());
 }
 
 struct CheckValueAfterMovingGCFunctor : public VoidDefaultAdaptor<Value> {
     template <typename T> void operator()(T* t) { CheckGCThingAfterMovingGC(t); }
 };
 
 inline void
 CheckValueAfterMovingGC(const JS::Value& value)
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -160,16 +160,19 @@ struct Runtime
 
   public:
     Runtime()
       : heapState_(JS::HeapState::Idle)
       , gcStoreBufferPtr_(nullptr)
     {}
 
     bool isHeapBusy() const { return heapState_ != JS::HeapState::Idle; }
+    bool isHeapMajorCollecting() const { return heapState_ == JS::HeapState::MajorCollecting; }
+    bool isHeapMinorCollecting() const { return heapState_ == JS::HeapState::MinorCollecting; }
+    bool isHeapCollecting() const { return isHeapMinorCollecting() || isHeapMajorCollecting(); }
 
     js::gc::StoreBuffer* gcStoreBufferPtr() { return gcStoreBufferPtr_; }
 
     static JS::shadow::Runtime* asShadowRuntime(JSRuntime* rt) {
         return reinterpret_cast<JS::shadow::Runtime*>(rt);
     }
 
   protected:
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -1697,20 +1697,20 @@ Debugger::slowPathOnIonCompilation(JSCon
         cx->clearPendingException();
         return;
     }
 
     MOZ_ASSERT(status == JSTRAP_CONTINUE);
 }
 
 bool
-Debugger::isDebuggee(const JSCompartment* compartment) const
+Debugger::isDebuggeeUnbarriered(const JSCompartment* compartment) const
 {
     MOZ_ASSERT(compartment);
-    return compartment->isDebuggee() && debuggees.has(compartment->maybeGlobal());
+    return compartment->isDebuggee() && debuggees.has(compartment->unsafeUnbarrieredMaybeGlobal());
 }
 
 Debugger::TenurePromotionsLogEntry::TenurePromotionsLogEntry(JSRuntime* rt, JSObject& obj, double when)
   : className(obj.getClass()->name),
     when(when),
     frame(getObjectAllocationSite(obj)),
     size(JS::ubi::Node(&obj).size(rt->debuggerMallocSizeOf))
 { }
@@ -2466,17 +2466,17 @@ Debugger::markAllIteratively(GCMarker* t
 
     /*
      * Find all Debugger objects in danger of GC. This code is a little
      * convoluted since the easiest way to find them is via their debuggees.
      */
     JSRuntime* rt = trc->runtime();
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         if (c->isDebuggee()) {
-            GlobalObject* global = c->maybeGlobal();
+            GlobalObject* global = c->unsafeUnbarrieredMaybeGlobal();
             if (!IsMarkedUnbarriered(&global))
                 continue;
 
             /*
              * Every debuggee has at least one debugger, so in this case
              * getDebuggers can't return nullptr.
              */
             const GlobalObject::DebuggerVector* debuggers = global->getDebuggers();
@@ -2533,20 +2533,20 @@ Debugger::markAllIteratively(GCMarker* t
  */
 /* static */ void
 Debugger::markAll(JSTracer* trc)
 {
     JSRuntime* rt = trc->runtime();
     for (Debugger* dbg : rt->debuggerList) {
         WeakGlobalObjectSet& debuggees = dbg->debuggees;
         for (WeakGlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) {
-            GlobalObject* global = e.front();
+            GlobalObject* global = e.front().unbarrieredGet();
             TraceManuallyBarrieredEdge(trc, &global, "Global Object");
-            if (global != e.front())
-                e.rekeyFront(ReadBarrieredGlobalObject(global));
+            if (global != e.front().unbarrieredGet())
+                e.rekeyFront(global, ReadBarrieredGlobalObject(global));
         }
 
         HeapPtrNativeObject& dbgobj = dbg->toJSObjectRef();
         TraceEdge(trc, &dbgobj, "Debugger Object");
 
         dbg->scripts.trace(trc);
         dbg->sources.trace(trc);
         dbg->objects.trace(trc);
@@ -2610,17 +2610,17 @@ Debugger::sweepAll(FreeOp* fop)
     for (Debugger* dbg : rt->debuggerList) {
         if (IsAboutToBeFinalized(&dbg->object)) {
             /*
              * dbg is being GC'd. Detach it from its debuggees. The debuggee
              * might be GC'd too. Since detaching requires access to both
              * objects, this must be done before finalize time.
              */
             for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
-                dbg->removeDebuggeeGlobal(fop, e.front(), &e);
+                dbg->removeDebuggeeGlobal(fop, e.front().unbarrieredGet(), &e);
         }
     }
 }
 
 /* static */ void
 Debugger::detachAllDebuggersFromGlobal(FreeOp* fop, GlobalObject* global)
 {
     const GlobalObject::DebuggerVector* debuggers = global->getDebuggers();
@@ -3450,17 +3450,17 @@ Debugger::addDebuggeeGlobal(JSContext* c
 }
 
 void
 Debugger::recomputeDebuggeeZoneSet()
 {
     AutoEnterOOMUnsafeRegion oomUnsafe;
     debuggeeZones.clear();
     for (auto range = debuggees.all(); !range.empty(); range.popFront()) {
-        if (!debuggeeZones.put(range.front()->zone()))
+        if (!debuggeeZones.put(range.front().unbarrieredGet()->zone()))
             oomUnsafe.crash("Debugger::removeDebuggeeGlobal");
     }
 }
 
 template<typename V>
 static Debugger**
 findDebuggerInVector(Debugger* dbg, V* vec)
 {
@@ -3479,17 +3479,17 @@ Debugger::removeDebuggeeGlobal(FreeOp* f
 {
     /*
      * The caller might have found global by enumerating this->debuggees; if
      * so, use HashSet::Enum::removeFront rather than HashSet::remove below,
      * to avoid invalidating the live enumerator.
      */
     MOZ_ASSERT(debuggees.has(global));
     MOZ_ASSERT(debuggeeZones.has(global->zone()));
-    MOZ_ASSERT_IF(debugEnum, debugEnum->front() == global);
+    MOZ_ASSERT_IF(debugEnum, debugEnum->front().unbarrieredGet() == global);
 
     /*
      * FIXME Debugger::slowPathOnLeaveFrame needs to kill all Debugger.Frame
      * objects referring to a particular JS stack frame. This is hard if
      * Debugger objects that are no longer debugging the relevant global might
      * have live Frame objects. So we take the easy way out and kill them here.
      * This is a bug, since it's observable and contrary to the spec. One
      * possible fix would be to put such objects into a compartment-wide bag
@@ -4098,17 +4098,17 @@ class MOZ_STACK_CLASS Debugger::ObjectQu
          * exist some path from this non-debuggee node back to a node in our
          * debuggee compartments. However, if that were true, then the incoming
          * cross compartment edge back into a debuggee compartment is already
          * listed as an edge in the RootList we started traversal with, and
          * therefore we don't need to follow edges to or from this non-debuggee
          * node.
          */
         JSCompartment* comp = referent.compartment();
-        if (comp && !dbg->isDebuggee(comp)) {
+        if (comp && !dbg->isDebuggeeUnbarriered(comp)) {
             traversal.abandonReferent();
             return true;
         }
 
         /*
          * If the referent is an object and matches our query's restrictions,
          * add it to the vector accumulating results. Skip objects that should
          * never be exposed to JS, like ScopeObjects and internal functions.
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -254,17 +254,17 @@ class Debugger : private mozilla::Linked
     // must be 0 and Observing must be 1.
     enum IsObserving {
         NotObserving = 0,
         Observing = 1
     };
 
     // Return true if the given compartment is a debuggee of this debugger,
     // false otherwise.
-    bool isDebuggee(const JSCompartment* compartment) const;
+    bool isDebuggeeUnbarriered(const JSCompartment* compartment) const;
 
     // Return true if this Debugger observed a debuggee that participated in the
     // GC identified by the given GC number. Return false otherwise.
     bool observedGC(uint64_t majorGCNumber) const {
         return observedGCs.has(majorGCNumber);
     }
 
     // Notify this Debugger that one or more of its debuggees is participating
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -395,18 +395,18 @@ struct ObjectGroupCompartment::NewEntry
 
     static inline HashNumber hash(const Lookup& lookup) {
         return PointerHasher<JSObject*, 3>::hash(lookup.hashProto.raw()) ^
                PointerHasher<const Class*, 3>::hash(lookup.clasp) ^
                PointerHasher<JSObject*, 3>::hash(lookup.associated);
     }
 
     static inline bool match(const NewEntry& key, const Lookup& lookup) {
-        return key.group->proto() == lookup.matchProto &&
-               (!lookup.clasp || key.group->clasp() == lookup.clasp) &&
+        return key.group.unbarrieredGet()->proto() == lookup.matchProto &&
+               (!lookup.clasp || key.group.unbarrieredGet()->clasp() == lookup.clasp) &&
                key.associated == lookup.associated;
     }
 
     static void rekey(NewEntry& k, const NewEntry& newKey) { k = newKey; }
 };
 
 // This class is used to add a post barrier on a NewTable entry, as the key is
 // calculated from a prototype object which may be moved by generational GC.
@@ -1804,31 +1804,31 @@ ObjectGroupCompartment::fixupNewTableAft
     /*
      * Each entry's hash depends on the object's prototype and we can't tell
      * whether that has been moved or not in sweepNewObjectGroupTable().
      */
     if (table && table->initialized()) {
         for (NewTable::Enum e(*table); !e.empty(); e.popFront()) {
             NewEntry entry = e.front();
             bool needRekey = false;
-            if (IsForwarded(entry.group.get())) {
-                entry.group.set(Forwarded(entry.group.get()));
+            if (IsForwarded(entry.group.unbarrieredGet())) {
+                entry.group.set(Forwarded(entry.group.unbarrieredGet()));
                 needRekey = true;
             }
-            TaggedProto proto = entry.group->proto();
+            TaggedProto proto = entry.group.unbarrieredGet()->proto();
             if (proto.isObject() && IsForwarded(proto.toObject())) {
                 proto = TaggedProto(Forwarded(proto.toObject()));
                 needRekey = true;
             }
             if (entry.associated && IsForwarded(entry.associated)) {
                 entry.associated = Forwarded(entry.associated);
                 needRekey = true;
             }
             if (needRekey) {
-                const Class* clasp = entry.group->clasp();
+                const Class* clasp = entry.group.unbarrieredGet()->clasp();
                 if (entry.associated && entry.associated->is<JSFunction>())
                     clasp = nullptr;
                 NewEntry::Lookup lookup(clasp, proto, entry.associated);
                 e.rekeyFront(lookup, entry);
             }
         }
     }
 }
@@ -1842,23 +1842,23 @@ ObjectGroupCompartment::checkNewTableAft
      * Assert that nothing points into the nursery or needs to be relocated, and
      * that the hash table entries are discoverable.
      */
     if (!table || !table->initialized())
         return;
 
     for (NewTable::Enum e(*table); !e.empty(); e.popFront()) {
         NewEntry entry = e.front();
-        CheckGCThingAfterMovingGC(entry.group.get());
-        TaggedProto proto = entry.group->proto();
+        CheckGCThingAfterMovingGC(entry.group.unbarrieredGet());
+        TaggedProto proto = entry.group.unbarrieredGet()->proto();
         if (proto.isObject())
             CheckGCThingAfterMovingGC(proto.toObject());
         CheckGCThingAfterMovingGC(entry.associated);
 
-        const Class* clasp = entry.group->clasp();
+        const Class* clasp = entry.group.unbarrieredGet()->clasp();
         if (entry.associated && entry.associated->is<JSFunction>())
             clasp = nullptr;
 
         NewEntry::Lookup lookup(clasp, proto, entry.associated);
         NewTable::Ptr ptr = table->lookup(lookup);
         MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front());
     }
 }
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1030,20 +1030,16 @@ struct JSRuntime : public JS::shadow::Ru
 #endif
 
     /* Garbage collector state, used by jsgc.c. */
     js::gc::GCRuntime   gc;
 
     /* Garbage collector state has been sucessfully initialized. */
     bool                gcInitialized;
 
-    bool isHeapMajorCollecting() const { return heapState_ == JS::HeapState::MajorCollecting; }
-    bool isHeapMinorCollecting() const { return heapState_ == JS::HeapState::MinorCollecting; }
-    bool isHeapCollecting() const { return isHeapMinorCollecting() || isHeapMajorCollecting(); }
-
     int gcZeal() { return gc.zeal(); }
 
     void lockGC() {
         assertCanLock(js::GCLock);
         gc.lockGC();
     }
 
     void unlockGC() {
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -531,38 +531,47 @@ SavedFrameSubsumedByCaller(JSContext* cx
 }
 
 // Return the first SavedFrame in the chain that starts with |frame| whose
 // principals are subsumed by |principals|, according to |subsumes|. If there is
 // no such frame, return nullptr. |skippedAsync| is set to true if any of the
 // skipped frames had the |asyncCause| property set, otherwise it is explicitly
 // set to false.
 static SavedFrame*
-GetFirstSubsumedFrame(JSContext* cx, HandleSavedFrame frame, bool& skippedAsync)
+GetFirstSubsumedFrame(JSContext* cx, HandleSavedFrame frame, JS::SavedFrameSelfHosted selfHosted,
+                      bool& skippedAsync)
 {
     skippedAsync = false;
 
     RootedSavedFrame rootedFrame(cx, frame);
-    while (rootedFrame && !SavedFrameSubsumedByCaller(cx, rootedFrame)) {
+    while (rootedFrame) {
+        if ((selfHosted == JS::SavedFrameSelfHosted::Include || !rootedFrame->isSelfHosted()) &&
+            SavedFrameSubsumedByCaller(cx, rootedFrame))
+        {
+            return rootedFrame;
+        }
+
         if (rootedFrame->getAsyncCause())
             skippedAsync = true;
+
         rootedFrame = rootedFrame->getParent();
     }
 
-    return rootedFrame;
+    return nullptr;
 }
 
 JS_FRIEND_API(JSObject*)
-GetFirstSubsumedSavedFrame(JSContext* cx, HandleObject savedFrame)
+GetFirstSubsumedSavedFrame(JSContext* cx, HandleObject savedFrame,
+                           JS::SavedFrameSelfHosted selfHosted)
 {
     if (!savedFrame)
         return nullptr;
     bool skippedAsync;
     RootedSavedFrame frame(cx, &savedFrame->as<SavedFrame>());
-    return GetFirstSubsumedFrame(cx, frame, skippedAsync);
+    return GetFirstSubsumedFrame(cx, frame, selfHosted, skippedAsync);
 }
 
 /* static */ bool
 SavedFrame::checkThis(JSContext* cx, CallArgs& args, const char* fnName,
                       MutableHandleObject frame)
 {
     const Value& thisValue = args.thisv();
 
@@ -654,144 +663,157 @@ public:
  private:
     Maybe<JSAutoCompartment> ac_;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 } // namespace
 
 static inline js::SavedFrame*
-UnwrapSavedFrame(JSContext* cx, HandleObject obj, bool& skippedAsync)
+UnwrapSavedFrame(JSContext* cx, HandleObject obj, SavedFrameSelfHosted selfHosted,
+                 bool& skippedAsync)
 {
     if (!obj)
         return nullptr;
+
     RootedObject savedFrameObj(cx, CheckedUnwrap(obj));
-    MOZ_ASSERT(savedFrameObj);
+    if (!savedFrameObj)
+        return nullptr;
+
     MOZ_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*savedFrameObj));
     js::RootedSavedFrame frame(cx, &savedFrameObj->as<js::SavedFrame>());
-    return GetFirstSubsumedFrame(cx, frame, skippedAsync);
+    return GetFirstSubsumedFrame(cx, frame, selfHosted, skippedAsync);
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameSource(JSContext* cx, HandleObject savedFrame, MutableHandleString sourcep)
+GetSavedFrameSource(JSContext* cx, HandleObject savedFrame, MutableHandleString sourcep,
+                    SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         sourcep.set(cx->runtime()->emptyString);
         return SavedFrameResult::AccessDenied;
     }
     sourcep.set(frame->getSource());
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameLine(JSContext* cx, HandleObject savedFrame, uint32_t* linep)
+GetSavedFrameLine(JSContext* cx, HandleObject savedFrame, uint32_t* linep,
+                  SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     MOZ_ASSERT(linep);
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         *linep = 0;
         return SavedFrameResult::AccessDenied;
     }
     *linep = frame->getLine();
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameColumn(JSContext* cx, HandleObject savedFrame, uint32_t* columnp)
+GetSavedFrameColumn(JSContext* cx, HandleObject savedFrame, uint32_t* columnp,
+                    SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     MOZ_ASSERT(columnp);
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         *columnp = 0;
         return SavedFrameResult::AccessDenied;
     }
     *columnp = frame->getColumn();
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameFunctionDisplayName(JSContext* cx, HandleObject savedFrame, MutableHandleString namep)
+GetSavedFrameFunctionDisplayName(JSContext* cx, HandleObject savedFrame, MutableHandleString namep,
+                                 SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         namep.set(nullptr);
         return SavedFrameResult::AccessDenied;
     }
     namep.set(frame->getFunctionDisplayName());
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameAsyncCause(JSContext* cx, HandleObject savedFrame, MutableHandleString asyncCausep)
+GetSavedFrameAsyncCause(JSContext* cx, HandleObject savedFrame, MutableHandleString asyncCausep,
+                        SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         asyncCausep.set(nullptr);
         return SavedFrameResult::AccessDenied;
     }
     asyncCausep.set(frame->getAsyncCause());
     if (!asyncCausep && skippedAsync)
         asyncCausep.set(cx->names().Async);
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameAsyncParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject asyncParentp)
+GetSavedFrameAsyncParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject asyncParentp,
+                         SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         asyncParentp.set(nullptr);
         return SavedFrameResult::AccessDenied;
     }
     js::RootedSavedFrame parent(cx, frame->getParent());
 
     // The current value of |skippedAsync| is not interesting, because we are
     // interested in whether we would cross any async parents to get from here
     // to the first subsumed parent frame instead.
-    js::RootedSavedFrame subsumedParent(cx, GetFirstSubsumedFrame(cx, parent, skippedAsync));
+    js::RootedSavedFrame subsumedParent(cx, GetFirstSubsumedFrame(cx, parent, selfHosted,
+                                                                  skippedAsync));
 
     // Even if |parent| is not subsumed, we still want to return a pointer to it
     // rather than |subsumedParent| so it can pick up any |asyncCause| from the
     // inaccessible part of the chain.
     if (subsumedParent && (subsumedParent->getAsyncCause() || skippedAsync))
         asyncParentp.set(parent);
     else
         asyncParentp.set(nullptr);
     return SavedFrameResult::Ok;
 }
 
 JS_PUBLIC_API(SavedFrameResult)
-GetSavedFrameParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject parentp)
+GetSavedFrameParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject parentp,
+                    SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */)
 {
     AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
     bool skippedAsync;
-    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+    js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync));
     if (!frame) {
         parentp.set(nullptr);
         return SavedFrameResult::AccessDenied;
     }
     js::RootedSavedFrame parent(cx, frame->getParent());
 
     // The current value of |skippedAsync| is not interesting, because we are
     // interested in whether we would cross any async parents to get from here
     // to the first subsumed parent frame instead.
-    js::RootedSavedFrame subsumedParent(cx, GetFirstSubsumedFrame(cx, parent, skippedAsync));
+    js::RootedSavedFrame subsumedParent(cx, GetFirstSubsumedFrame(cx, parent, selfHosted,
+                                                                  skippedAsync));
 
     // Even if |parent| is not subsumed, we still want to return a pointer to it
     // rather than |subsumedParent| so it can pick up any |asyncCause| from the
     // inaccessible part of the chain.
     if (subsumedParent && !(subsumedParent->getAsyncCause() || skippedAsync))
         parentp.set(parent);
     else
         parentp.set(nullptr);
@@ -805,49 +827,49 @@ BuildStackString(JSContext* cx, HandleOb
 
     // Enter a new block to constrain the scope of possibly entering the stack's
     // compartment. This ensures that when we finish the StringBuffer, we are
     // back in the cx's original compartment, and fulfill our contract with
     // callers to place the output string in the cx's current compartment.
     {
         AutoMaybeEnterFrameCompartment ac(cx, stack);
         bool skippedAsync;
-        js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, stack, skippedAsync));
+        js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, stack, SavedFrameSelfHosted::Exclude,
+                                                        skippedAsync));
         if (!frame) {
             stringp.set(cx->runtime()->emptyString);
             return true;
         }
 
         js::RootedSavedFrame parent(cx);
         do {
             MOZ_ASSERT(SavedFrameSubsumedByCaller(cx, frame));
+            MOZ_ASSERT(!frame->isSelfHosted());
 
-            if (!frame->isSelfHosted()) {
-                RootedString asyncCause(cx, frame->getAsyncCause());
-                if (!asyncCause && skippedAsync)
-                    asyncCause.set(cx->names().Async);
+            RootedString asyncCause(cx, frame->getAsyncCause());
+            if (!asyncCause && skippedAsync)
+                asyncCause.set(cx->names().Async);
 
-                js::RootedAtom name(cx, frame->getFunctionDisplayName());
-                if ((indent && !sb.appendN(' ', indent))
-                    || (asyncCause && (!sb.append(asyncCause) || !sb.append('*')))
-                    || (name && !sb.append(name))
-                    || !sb.append('@')
-                    || !sb.append(frame->getSource())
-                    || !sb.append(':')
-                    || !NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb)
-                    || !sb.append(':')
-                    || !NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb)
-                    || !sb.append('\n'))
-                {
-                    return false;
-                }
+            js::RootedAtom name(cx, frame->getFunctionDisplayName());
+            if ((indent && !sb.appendN(' ', indent))
+                || (asyncCause && (!sb.append(asyncCause) || !sb.append('*')))
+                || (name && !sb.append(name))
+                || !sb.append('@')
+                || !sb.append(frame->getSource())
+                || !sb.append(':')
+                || !NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb)
+                || !sb.append(':')
+                || !NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb)
+                || !sb.append('\n'))
+            {
+                return false;
             }
 
             parent = frame->getParent();
-            frame = js::GetFirstSubsumedFrame(cx, parent, skippedAsync);
+            frame = js::GetFirstSubsumedFrame(cx, parent, SavedFrameSelfHosted::Exclude, skippedAsync);
         } while (frame);
     }
 
     JSString* str = sb.finishString();
     if (!str)
         return false;
     assertSameCompartment(cx, str);
     stringp.set(str);
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -2259,17 +2259,17 @@ DebugScopes::sweep(JSRuntime* rt)
              * that the synthetic SO is also about to be finalized too, and thus
              * the loop below will take care of things. But complex GC behavior
              * means that marks are only conservative approximations of
              * liveness; we should assume that anything could be marked.
              *
              * Thus, we must explicitly remove the entries from both liveScopes
              * and missingScopes here.
              */
-            liveScopes.remove(&e.front().value()->scope());
+            liveScopes.remove(&e.front().value().unbarrieredGet()->scope());
             e.removeFront();
         } else {
             MissingScopeKey key = e.front().key();
             if (IsForwarded(key.staticScope())) {
                 key.updateStaticScope(Forwarded(key.staticScope()));
                 e.rekeyFront(key);
             }
         }
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1657,16 +1657,18 @@ static const JSFunctionSpec intrinsic_fu
           CallNonGenericSelfhostedMethod<Is<TypedArrayObject>>, 2, 0),
 
     JS_FN("CallLegacyGeneratorMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<LegacyGeneratorObject>>, 2, 0),
     JS_FN("CallStarGeneratorMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<StarGeneratorObject>>, 2, 0),
 
     JS_FN("IsWeakSet",               intrinsic_IsWeakSet,               1,0),
+    JS_FN("CallWeakSetMethodIfWrapped",
+          CallNonGenericSelfhostedMethod<Is<WeakSetObject>>, 2, 0),
 
     // See builtin/TypedObject.h for descriptors of the typedobj functions.
     JS_FN("NewOpaqueTypedObject",           js::NewOpaqueTypedObject, 1, 0),
     JS_FN("NewDerivedTypedObject",          js::NewDerivedTypedObject, 3, 0),
     JS_FN("TypedObjectBuffer",              TypedObject::GetBuffer, 1, 0),
     JS_FN("TypedObjectByteOffset",          TypedObject::GetByteOffset, 1, 0),
     JS_FN("AttachTypedObject",              js::AttachTypedObject, 3, 0),
     JS_FN("TypedObjectIsAttached",          js::TypedObjectIsAttached, 1, 0),
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -1200,19 +1200,20 @@ Shape::setObjectFlags(ExclusiveContext* 
 StackBaseShape::hash(const Lookup& lookup)
 {
     HashNumber hash = lookup.flags;
     hash = RotateLeft(hash, 4) ^ (uintptr_t(lookup.clasp) >> 3);
     return hash;
 }
 
 /* static */ inline bool
-StackBaseShape::match(UnownedBaseShape* key, const Lookup& lookup)
+StackBaseShape::match(ReadBarriered<UnownedBaseShape*> key, const Lookup& lookup)
 {
-    return key->flags == lookup.flags && key->clasp_ == lookup.clasp;
+    return key.unbarrieredGet()->flags == lookup.flags &&
+           key.unbarrieredGet()->clasp_ == lookup.clasp;
 }
 
 inline
 BaseShape::BaseShape(const StackBaseShape& base)
   : clasp_(base.clasp),
     compartment_(base.compartment),
     flags(base.flags),
     slotSpan_(0),
@@ -1446,17 +1447,17 @@ JSCompartment::checkInitialShapesTableAf
     /*
      * Assert that the postbarriers have worked and that nothing is left in
      * initialShapes that points into the nursery, and that the hash table
      * entries are discoverable.
      */
     for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) {
         InitialShapeEntry entry = e.front();
         TaggedProto proto = entry.proto;
-        Shape* shape = entry.shape.get();
+        Shape* shape = entry.shape.unbarrieredGet();
 
         if (proto.isObject())
             CheckGCThingAfterMovingGC(proto.toObject());
 
         InitialShapeEntry::Lookup lookup(shape->getObjectClass(),
                                          proto,
                                          shape->numFixedSlots(),
                                          shape->getObjectFlags());
@@ -1626,29 +1627,29 @@ void
 JSCompartment::fixupInitialShapeTable()
 {
     if (!initialShapes.initialized())
         return;
 
     for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) {
         InitialShapeEntry entry = e.front();
         bool needRekey = false;
-        if (IsForwarded(entry.shape.get())) {
-            entry.shape.set(Forwarded(entry.shape.get()));
+        if (IsForwarded(entry.shape.unbarrieredGet())) {
+            entry.shape.set(Forwarded(entry.shape.unbarrieredGet()));
             needRekey = true;
         }
         if (entry.proto.isObject() && IsForwarded(entry.proto.toObject())) {
             entry.proto = TaggedProto(Forwarded(entry.proto.toObject()));
             needRekey = true;
         }
         if (needRekey) {
-            InitialShapeEntry::Lookup relookup(entry.shape->getObjectClass(),
+            InitialShapeEntry::Lookup relookup(entry.shape.unbarrieredGet()->getObjectClass(),
                                                entry.proto,
-                                               entry.shape->numFixedSlots(),
-                                               entry.shape->getObjectFlags());
+                                               entry.shape.unbarrieredGet()->numFixedSlots(),
+                                               entry.shape.unbarrieredGet()->getObjectFlags());
             e.rekeyFront(relookup, entry);
         }
     }
 }
 
 void
 AutoRooterGetterSetter::Inner::trace(JSTracer* trc)
 {
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -476,17 +476,17 @@ BaseShape::toUnowned()
 UnownedBaseShape*
 BaseShape::baseUnowned()
 {
     MOZ_ASSERT(isOwned() && unowned_);
     return unowned_;
 }
 
 /* Entries for the per-compartment baseShapes set of unowned base shapes. */
-struct StackBaseShape : public DefaultHasher<ReadBarrieredUnownedBaseShape>
+struct StackBaseShape : public DefaultHasher<ReadBarriered<UnownedBaseShape*>>
 {
     uint32_t flags;
     const Class* clasp;
     JSCompartment* compartment;
 
     explicit StackBaseShape(BaseShape* base)
       : flags(base->flags & BaseShape::OBJECT_FLAG_MASK),
         clasp(base->clasp_),
@@ -508,20 +508,20 @@ struct StackBaseShape : public DefaultHa
         MOZ_IMPLICIT Lookup(UnownedBaseShape* base)
           : flags(base->getObjectFlags()), clasp(base->clasp())
         {
             MOZ_ASSERT(!base->isOwned());
         }
     };
 
     static inline HashNumber hash(const Lookup& lookup);
-    static inline bool match(UnownedBaseShape* key, const Lookup& lookup);
+    static inline bool match(ReadBarriered<UnownedBaseShape*> key, const Lookup& lookup);
 };
 
-typedef HashSet<ReadBarrieredUnownedBaseShape,
+typedef HashSet<ReadBarriered<UnownedBaseShape*>,
                 StackBaseShape,
                 SystemAllocPolicy> BaseShapeSet;
 
 
 class Shape : public gc::TenuredCell
 {
     friend class ::JSObject;
     friend class ::JSFunction;
--- a/js/src/vm/Symbol.cpp
+++ b/js/src/vm/Symbol.cpp
@@ -104,21 +104,21 @@ Symbol::dump(FILE* fp)
     }
 }
 #endif  // DEBUG
 
 void
 SymbolRegistry::sweep()
 {
     for (Enum e(*this); !e.empty(); e.popFront()) {
-        Symbol* sym = e.front();
-        if (IsAboutToBeFinalizedUnbarriered(&sym))
+        mozilla::DebugOnly<Symbol*> sym = e.front().unbarrieredGet();
+        if (IsAboutToBeFinalized(&e.mutableFront()))
             e.removeFront();
         else
-            MOZ_ASSERT(sym == e.front());
+            MOZ_ASSERT(sym == e.front().unbarrieredGet());
     }
 }
 
 bool
 js::SymbolDescriptiveString(JSContext* cx, Symbol* sym, MutableHandleValue result)
 {
     // steps 2-5
     StringBuffer sb(cx);
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -3376,17 +3376,17 @@ PreliminaryObjectArray::sweep()
         if (*ptr && IsAboutToBeFinalizedUnbarriered(ptr)) {
             // Before we clear this reference, change the object's group to the
             // Object.prototype group. This is done to ensure JSObject::finalize
             // sees a NativeObject Class even if we change the current group's
             // Class to one of the unboxed object classes in the meantime. If
             // the compartment's global is dead, we don't do anything as the
             // group's Class is not going to change in that case.
             JSObject* obj = *ptr;
-            GlobalObject* global = obj->compartment()->maybeGlobal();
+            GlobalObject* global = obj->compartment()->unsafeUnbarrieredMaybeGlobal();
             if (global && !obj->isSingleton()) {
                 JSObject* objectProto = GetBuiltinPrototypePure(global, JSProto_Object);
                 obj->setGroup(objectProto->groupRaw());
                 MOZ_ASSERT(obj->is<NativeObject>());
                 MOZ_ASSERT(obj->getClass() == objectProto->getClass());
                 MOZ_ASSERT(!obj->getClass()->finalize);
             }
 
@@ -4069,17 +4069,18 @@ ConstraintTypeSet::sweep(Zone* zone, Aut
                     *pentry = key;
                 } else {
                     oom.setOOM();
                     flags |= TYPE_FLAG_ANYOBJECT;
                     clearObjects();
                     objectCount = 0;
                     break;
                 }
-            } else if (key->isGroup() && key->group()->unknownPropertiesDontCheckGeneration()) {
+            } else if (key->isGroup() &&
+                       key->groupNoBarrier()->unknownPropertiesDontCheckGeneration()) {
                 // Object sets containing objects with unknown properties might
                 // not be complete. Mark the type set as unknown, which it will
                 // be treated as during Ion compilation.
                 //
                 // Note that we don't have to do this when the type set might
                 // be missing the native group corresponding to an unboxed
                 // object group. In this case, the native group points to the
                 // unboxed object group via its addendum, so as long as objects
@@ -4093,17 +4094,17 @@ ConstraintTypeSet::sweep(Zone* zone, Aut
         setBaseObjectCount(objectCount);
     } else if (objectCount == 1) {
         ObjectKey* key = (ObjectKey*) objectSet;
         if (!IsObjectKeyAboutToBeFinalized(&key)) {
             objectSet = reinterpret_cast<ObjectKey**>(key);
         } else {
             // As above, mark type sets containing objects with unknown
             // properties as unknown.
-            if (key->isGroup() && key->group()->unknownPropertiesDontCheckGeneration())
+            if (key->isGroup() && key->groupNoBarrier()->unknownPropertiesDontCheckGeneration())
                 flags |= TYPE_FLAG_ANYOBJECT;
             objectSet = nullptr;
             setBaseObjectCount(0);
         }
     }
 
     /*
      * Type constraints only hold weak references. Copy constraints referring
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -7700,91 +7700,77 @@ nsRuleNode::ComputePositionData(void* aS
   // We allow the enumerated box size property values -moz-min-content, etc. to
   // be specified on both the {,min-,max-}width properties and the
   // {,min-,max-}height properties, regardless of the writing mode.  This is
   // because the writing mode is not determined until here, at computed value
   // time.  Since we do not support layout behavior of these keywords on the
   // block-axis properties, we turn them into unset if we find them in
   // that case.
 
-  bool vertical;
-  switch (aContext->StyleVisibility()->mWritingMode) {
-    default:
-      MOZ_ASSERT(false, "unexpected writing-mode value");
-      // fall through
-    case NS_STYLE_WRITING_MODE_HORIZONTAL_TB:
-      vertical = false;
-      break;
-    case NS_STYLE_WRITING_MODE_VERTICAL_RL:
-    case NS_STYLE_WRITING_MODE_VERTICAL_LR:
-    case NS_STYLE_WRITING_MODE_SIDEWAYS_RL:
-    case NS_STYLE_WRITING_MODE_SIDEWAYS_LR:
-      vertical = true;
-      break;
-  }
-  uint8_t wm = WritingMode(aContext).GetBits();
+  WritingMode wm(aContext);
+  bool vertical = wm.IsVertical();
 
   const nsCSSValue* width = aRuleData->ValueForWidth();
   if (width->GetUnit() == eCSSUnit_Enumerated) {
-    conditions.SetWritingModeDependency(wm);
+    conditions.SetWritingModeDependency(wm.GetBits());
   }
   SetCoord(width->GetUnit() == eCSSUnit_Enumerated && vertical ?
              nsCSSValue(eCSSUnit_Unset) : *width,
            pos->mWidth, parentPos->mWidth,
            SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
              SETCOORD_UNSET_INITIAL,
            aContext, mPresContext, conditions);
 
   const nsCSSValue* minWidth = aRuleData->ValueForMinWidth();
   if (minWidth->GetUnit() == eCSSUnit_Enumerated) {
-    conditions.SetWritingModeDependency(wm);
+    conditions.SetWritingModeDependency(wm.GetBits());
   }
   SetCoord(minWidth->GetUnit() == eCSSUnit_Enumerated && vertical ?
              nsCSSValue(eCSSUnit_Unset) : *minWidth,
            pos->mMinWidth, parentPos->mMinWidth,
            SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
              SETCOORD_UNSET_INITIAL,
            aContext, mPresContext, conditions);
 
   const nsCSSValue* maxWidth = aRuleData->ValueForMaxWidth();
   if (maxWidth->GetUnit() == eCSSUnit_Enumerated) {
-    conditions.SetWritingModeDependency(wm);
+    conditions.SetWritingModeDependency(wm.GetBits());
   }
   SetCoord(maxWidth->GetUnit() == eCSSUnit_Enumerated && vertical ?
              nsCSSValue(eCSSUnit_Unset) : *maxWidth,
            pos->mMaxWidth, parentPos->mMaxWidth,
            SETCOORD_LPOEH | SETCOORD_INITIAL_NONE | SETCOORD_STORE_CALC |
              SETCOORD_UNSET_INITIAL,
            aContext, mPresContext, conditions);
 
   const nsCSSValue* height = aRuleData->ValueForHeight();
   if (height->GetUnit() == eCSSUnit_Enumerated) {
-    conditions.SetWritingModeDependency(wm);
+    conditions.SetWritingModeDependency(wm.GetBits());
   }
   SetCoord(height->GetUnit() == eCSSUnit_Enumerated && !vertical ?
              nsCSSValue(eCSSUnit_Unset) : *height,
            pos->mHeight, parentPos->mHeight,
            SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
              SETCOORD_UNSET_INITIAL,
            aContext, mPresContext, conditions);
 
   const nsCSSValue* minHeight = aRuleData->ValueForMinHeight();
   if (minHeight->GetUnit() == eCSSUnit_Enumerated) {
-    conditions.SetWritingModeDependency(wm);
+    conditions.SetWritingModeDependency(wm.GetBits());
   }
   SetCoord(minHeight->GetUnit() == eCSSUnit_Enumerated && !vertical ?
              nsCSSValue(eCSSUnit_Unset) : *minHeight,
            pos->mMinHeight, parentPos->mMinHeight,
            SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
              SETCOORD_UNSET_INITIAL,
            aContext, mPresContext, conditions);
 
   const nsCSSValue* maxHeight = aRuleData->ValueForMaxHeight();
   if (maxHeight->GetUnit() == eCSSUnit_Enumerated) {
-    conditions.SetWritingModeDependency(wm);
+    conditions.SetWritingModeDependency(wm.GetBits());
   }
   SetCoord(maxHeight->GetUnit() == eCSSUnit_Enumerated && !vertical ?
              nsCSSValue(eCSSUnit_Unset) : *maxHeight,
            pos->mMaxHeight, parentPos->mMaxHeight,
            SETCOORD_LPOEH | SETCOORD_INITIAL_NONE | SETCOORD_STORE_CALC |
              SETCOORD_UNSET_INITIAL,
            aContext, mPresContext, conditions);
 
--- a/media/libstagefright/binding/MP4Metadata.rs
+++ b/media/libstagefright/binding/MP4Metadata.rs
@@ -1,137 +1,170 @@
 // Module for parsing ISO Base Media Format aka video/mp4 streams.
 
 // 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 https://mozilla.org/MPL/2.0/.
 
+use std::fmt;
+
+/// Expose C api wrapper.
+pub mod capi;
+// FIXME: We can 'pub use capi::*' in rustc 1.5 and later.
+pub use capi::{mp4parse_new, mp4parse_free, mp4parse_read};
+
+#[derive(Clone, Copy, Eq, PartialEq)]
+pub struct FourCC(pub u32);
+
+impl fmt::Debug for FourCC {
+  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+    write!(f, "'{}'", fourcc_to_string(*self))
+  }
+}
+
 /// Basic ISO box structure.
+#[derive(Debug)]
 pub struct BoxHeader {
     /// Four character box type
-    pub name: u32,
+    pub name: FourCC,
     /// Size of the box in bytes
     pub size: u64,
     /// Offset to the start of the contained data (or header size).
     pub offset: u64,
 }
 
 /// File type box 'ftyp'.
+#[derive(Debug)]
 pub struct FileTypeBox {
-    name: u32,
+    name: FourCC,
     size: u64,
-    major_brand: u32,
+    major_brand: FourCC,
     minor_version: u32,
-    compatible_brands: Vec<u32>,
+    compatible_brands: Vec<FourCC>,
 }
 
 /// Movie header box 'mvhd'.
+#[derive(Debug)]
 pub struct MovieHeaderBox {
-    pub name: u32,
+    pub name: FourCC,
     pub size: u64,
     pub timescale: u32,
     pub duration: u64,
     // Ignore other fields.
 }
 
 /// Track header box 'tkhd'
+#[derive(Debug)]
 pub struct TrackHeaderBox {
-    pub name: u32,
+    pub name: FourCC,
     pub size: u64,
     pub track_id: u32,
     pub enabled: bool,
     pub duration: u64,
     pub width: u32,
     pub height: u32,
 }
 
 /// Edit list box 'elst'
+#[derive(Debug)]
 pub struct EditListBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     edits: Vec<Edit>,
 }
 
+#[derive(Debug)]
 pub struct Edit {
     segment_duration: u64,
     media_time: i64,
     media_rate_integer: i16,
     media_rate_fraction: i16,
 }
 
 /// Media header box 'mdhd'
+#[derive(Debug)]
 pub struct MediaHeaderBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     timescale: u32,
     duration: u64,
 }
 
 // Chunk offset box 'stco' or 'co64'
+#[derive(Debug)]
 pub struct ChunkOffsetBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     offsets: Vec<u64>,
 }
 
 // Sync sample box 'stss'
+#[derive(Debug)]
 pub struct SyncSampleBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     samples: Vec<u32>,
 }
 
 // Sample to chunk box 'stsc'
+#[derive(Debug)]
 pub struct SampleToChunkBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     samples: Vec<SampleToChunk>,
 }
 
+#[derive(Debug)]
 pub struct SampleToChunk {
     first_chunk: u32,
     samples_per_chunk: u32,
     sample_description_index: u32,
 }
 
 // Sample size box 'stsz'
+#[derive(Debug)]
 pub struct SampleSizeBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     sample_size: u32,
     sample_sizes: Vec<u32>,
 }
 
 // Time to sample box 'stts'
+#[derive(Debug)]
 pub struct TimeToSampleBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     samples: Vec<Sample>,
 }
 
+#[derive(Debug)]
 pub struct Sample {
     sample_count: u32,
     sample_delta: u32,
 }
 
 // Handler reference box 'hdlr'
+#[derive(Debug)]
 pub struct HandlerBox {
-    name: u32,
+    name: FourCC,
     size: u64,
-    handler_type: u32,
+    handler_type: FourCC,
 }
 
 // Sample description box 'stsd'
+#[derive(Debug)]
 pub struct SampleDescriptionBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     descriptions: Vec<SampleEntry>,
 }
 
 #[allow(dead_code)]
+#[derive(Debug)]
 enum SampleEntry {
     Audio {
         data_reference_index: u16,
         channelcount: u16,
         samplesize: u16,
         samplerate: u32,
         esds: ES_Descriptor,
     },
@@ -139,50 +172,63 @@ enum SampleEntry {
         data_reference_index: u16,
         width: u16,
         height: u16,
         avcc: AVCDecoderConfigurationRecord,
     },
 }
 
 #[allow(dead_code)]
+#[derive(Debug)]
 pub struct AVCDecoderConfigurationRecord {
     data: Vec<u8>,
 }
 
 #[allow(non_camel_case_types)]
 #[allow(dead_code)]
+#[derive(Debug)]
 pub struct ES_Descriptor {
     data: Vec<u8>,
 }
 
 /// Internal data structures.
+#[derive(Debug)]
 pub struct MediaContext {
-    pub tracks: Vec<Track>,
+    tracks: Vec<Track>,
 }
 
+impl MediaContext {
+    pub fn new() -> MediaContext {
+        MediaContext {
+            tracks: Vec::new(),
+        }
+    }
+}
+
+#[derive(Debug)]
 enum TrackType {
     Video,
     Audio
 }
 
+#[derive(Debug)]
 pub struct Track {
     track_type: TrackType,
 }
 
 mod byteorder; // 'extern crate' upstream.
 use byteorder::{BigEndian, ReadBytesExt};
 use std::io::{Read, BufRead, Take};
 use std::io::Cursor;
 use std::cmp;
 
 /// Parse a box out of a data buffer.
 pub fn read_box_header<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<BoxHeader> {
     let size32 = try!(be_u32(src));
-    let name = try!(be_u32(src));
+    let name = FourCC(try!(be_u32(src)));
     let size = match size32 {
         0 => panic!("unknown box size not implemented"),
         1 => {
             let size64 = try!(be_u64(src));
             assert!(size64 >= 16);
             size64
         },
         2 ... 7 => panic!("invalid box size"),
@@ -227,17 +273,17 @@ pub fn skip_remaining_box_content<T: Buf
 /// Helper to construct a Take over the contents of a box.
 fn limit<'a, T: Read>(f: &'a mut T, h: &BoxHeader) -> Take<&'a mut T> {
     f.take(h.size - h.offset)
 }
 
 /// Helper to construct a Cursor over the contents of a box.
 fn recurse<T: Read>(f: &mut T, h: &BoxHeader, context: &mut MediaContext) -> byteorder::Result<()> {
     use std::error::Error;
-    println!("{} -- recursing", h);
+    println!("{:?} -- recursing", h);
     // FIXME: I couldn't figure out how to do this without copying.
     // We use Seek on the Read we return in skip_box_content, but
     // that trait isn't implemented for a Take like our limit()
     // returns. Slurping the buffer and wrapping it in a Cursor
     // functions as a work around.
     let buf: Vec<u8> = f
         .bytes()
         .map(|u| u.unwrap())
@@ -249,159 +295,123 @@ fn recurse<T: Read>(f: &mut T, h: &BoxHe
             Err(byteorder::Error::UnexpectedEOF) => {
                 // byteorder returns EOF at the end of the buffer.
                 // This isn't an error for us, just an signal to
                 // stop recursion.
                 println!("Caught byteorder::Error::UnexpectedEOF");
                 break;
             },
             Err(byteorder::Error::Io(e)) => {
-                println!("I/O Error '{:?}' reading box: {}",
+                println!("I/O Error '{:?}' reading box: {:?}",
                          e.kind(), e.description());
                 return Err(byteorder::Error::Io(e));
             },
         }
     }
     assert!(content.position() == h.size - h.offset);
-    println!("{} -- end", h);
+    println!("{:?} -- end", h);
     Ok(())
 }
 
 /// Read the contents of a box, including sub boxes.
 /// Metadata is accumulated in the passed-through MediaContext struct.
 pub fn read_box<T: BufRead>(f: &mut T, context: &mut MediaContext) -> byteorder::Result<()> {
     read_box_header(f).and_then(|h| {
         let mut content = limit(f, &h);
         match &fourcc_to_string(h.name)[..] {
             "ftyp" => {
                 let ftyp = try!(read_ftyp(&mut content, &h));
-                println!("{}", ftyp);
+                println!("{:?}", ftyp);
             },
             "moov" => try!(recurse(&mut content, &h, context)),
             "mvhd" => {
                 let mvhd = try!(read_mvhd(&mut content, &h));
-                println!("  {}", mvhd);
+                println!("  {:?}", mvhd);
             },
             "trak" => try!(recurse(&mut content, &h, context)),
             "tkhd" => {
                 let tkhd = try!(read_tkhd(&mut content, &h));
-                println!("  {}", tkhd);
+                println!("  {:?}", tkhd);
             },
             "edts" => try!(recurse(&mut content, &h, context)),
             "elst" => {
                 let elst = try!(read_elst(&mut content, &h));
-                println!("  {}", elst);
+                println!("  {:?}", elst);
             },
             "mdia" => try!(recurse(&mut content, &h, context)),
             "mdhd" => {
                 let mdhd = try!(read_mdhd(&mut content, &h));
-                println!("  {}", mdhd);
+                println!("  {:?}", mdhd);
             },
             "minf" => try!(recurse(&mut content, &h, context)),
             "stbl" => try!(recurse(&mut content, &h, context)),
             "stco" => {
                 let stco = try!(read_stco(&mut content, &h));
-                println!("  {}", stco);
+                println!("  {:?}", stco);
             },
             "co64" => {
                 let co64 = try!(read_co64(&mut content, &h));
-                println!("  {}", co64);
+                println!("  {:?}", co64);
             },
             "stss" => {
                 let stss = try!(read_stss(&mut content, &h));
-                println!("  {}", stss);
+                println!("  {:?}", stss);
             },
             "stsc" => {
                 let stsc = try!(read_stsc(&mut content, &h));
-                println!("  {}", stsc);
+                println!("  {:?}", stsc);
             },
             "stsz" => {
                 let stsz = try!(read_stsz(&mut content, &h));
-                println!("  {}", stsz);
+                println!("  {:?}", stsz);
             },
             "stts" => {
                 let stts = try!(read_stts(&mut content, &h));
-                println!("  {}", stts);
+                println!("  {:?}", stts);
             },
             "hdlr" => {
                 let hdlr = try!(read_hdlr(&mut content, &h));
                 let track_type = match &fourcc_to_string(hdlr.handler_type)[..] {
                     "vide" => Some(TrackType::Video),
                     "soun" => Some(TrackType::Audio),
                     _ => None
                 };
                 // Save track types with recognized types.
                 match track_type {
                     Some(track_type) =>
                          context.tracks.push(Track { track_type: track_type }),
                     None => println!("unknown track type!"),
                 };
+                println!("  {:?}", hdlr);
             },
             "stsd" => {
                 let track = &context.tracks[context.tracks.len() - 1];
                 let stsd = try!(read_stsd(&mut content, &h, &track));
-                println!("  {}", stsd);
+                println!("  {:?}", stsd);
             },
             _ => {
                 // Skip the contents of unknown chunks.
-                println!("{} (skipped)", h);
+                println!("{:?} (skipped)", h);
                 try!(skip_box_content(&mut content, &h));
             },
         };
         assert!(content.limit() == 0);
-        println!("read_box context: {}", context);
+        println!("read_box context: {:?}", context);
         Ok(()) // and_then needs a Result to return.
     })
 }
 
-/// Entry point for C language callers.
-/// Take a buffer and call read_box() on it,
-/// returning the number of detected tracks.
-#[no_mangle]
-pub extern fn read_box_from_buffer(buffer: *const u8, size: usize) -> i32 {
-    use std::slice;
-    use std::thread;
-    use std::i32;
-
-    // Validate arguments from C.
-    if buffer.is_null() || size < 8 {
-        return -1;
-    }
-
-    // Wrap the buffer we've been give in a slice.
-    let b = unsafe { slice::from_raw_parts(buffer, size) };
-    let mut c = Cursor::new(b);
-
-    // Parse in a subthread.
-    let task = thread::spawn(move || {
-        let mut context = MediaContext { tracks: Vec::new() };
-        loop {
-            match read_box(&mut c, &mut context) {
-                Ok(_) => {},
-                Err(byteorder::Error::UnexpectedEOF) => { break },
-                Err(e) => { panic!(e) },
-            }
-        }
-        // Make sure the track count fits in an i32 so we can use
-        // negative values for failure.
-        assert!(context.tracks.len() < i32::MAX as usize);
-        context.tracks.len() as i32
-    });
-    // Catch any panics.
-    task.join().unwrap_or(-1)
-}
-
 /// Parse an ftyp box.
 pub fn read_ftyp<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> byteorder::Result<FileTypeBox> {
-    let major = try!(be_u32(src));
+    let major = FourCC(try!(be_u32(src)));
     let minor = try!(be_u32(src));
     let brand_count = (head.size - 8 - 8) / 4;
     let mut brands = Vec::new();
     for _ in 0..brand_count {
-        brands.push(try!(be_u32(src)));
+        brands.push(FourCC(try!(be_u32(src))));
     }
     Ok(FileTypeBox{
         name: head.name,
         size: head.size,
         major_brand: major,
         minor_version: minor,
         compatible_brands: brands,
     })
@@ -650,17 +660,17 @@ pub fn read_stts<T: ReadBytesExt>(src: &
 
 /// Parse a hdlr box.
 pub fn read_hdlr<T: ReadBytesExt + BufRead>(src: &mut T, head: &BoxHeader) -> byteorder::Result<HandlerBox> {
     let (_, _) = try!(read_fullbox_extra(src));
 
     // Skip uninteresting fields.
     try!(skip(src, 4));
 
-    let handler_type = try!(be_u32(src));
+    let handler_type = FourCC(try!(be_u32(src)));
 
     // Skip uninteresting fields.
     try!(skip(src, 12));
 
     // TODO(kinetik): Find a copy of ISO/IEC 14496-1 to work out how strings are encoded.
     // As a hack, just consume the rest of the box.
     try!(skip_remaining_box_content(src, head));
 
@@ -769,24 +779,24 @@ pub fn read_stsd<T: ReadBytesExt + BufRe
     Ok(SampleDescriptionBox{
         name: head.name,
         size: head.size,
         descriptions: descriptions,
     })
 }
 
 /// Convert the iso box type or other 4-character value to a string.
-fn fourcc_to_string(name: u32) -> String {
+fn fourcc_to_string(name: FourCC) -> String {
     let u32_to_vec = |u| {
         vec!((u >> 24 & 0xffu32) as u8,
              (u >> 16 & 0xffu32) as u8,
              (u >>  8 & 0xffu32) as u8,
              (u & 0xffu32) as u8)
     };
-    let name_bytes = u32_to_vec(name);
+    let name_bytes = u32_to_vec(name.0);
     String::from_utf8_lossy(&name_bytes).into_owned()
 }
 
 /// Skip a number of bytes that we don't care to parse.
 fn skip<T: BufRead>(src: &mut T, bytes: usize) -> byteorder::Result<usize> {
     let mut bytes_to_skip = bytes;
     while bytes_to_skip > 0 {
         let len = {
@@ -823,181 +833,41 @@ fn be_u16<T: ReadBytesExt>(src: &mut T) 
 fn be_u32<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<u32> {
     src.read_u32::<BigEndian>()
 }
 
 fn be_u64<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<u64> {
     src.read_u64::<BigEndian>()
 }
 
-use std::fmt;
-impl fmt::Display for BoxHeader {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "'{}' {} bytes", fourcc_to_string(self.name), self.size)
-    }
-}
-
-impl fmt::Display for FileTypeBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let name = fourcc_to_string(self.name);
-        let brand = fourcc_to_string(self.major_brand);
-        let mut compat = String::from("compatible with");
-        for brand in &self.compatible_brands {
-            let brand_string = fourcc_to_string(*brand);
-            compat.push(' ');
-            compat.push_str(&brand_string);
-        }
-        write!(f, "'{}' {} bytes '{}' v{}\n {}",
-               name, self.size, brand, self.minor_version, compat)
-    }
-}
-
-impl fmt::Display for MovieHeaderBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let name = fourcc_to_string(self.name);
-        write!(f, "'{}' {} bytes duration {}s", name, self.size,
-               (self.duration as f64)/(self.timescale as f64))
-    }
-}
-
-impl fmt::Display for MediaContext {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "Found {} tracks.", self.tracks.len())
-    }
-}
-
-use std::u16;
-impl fmt::Display for TrackHeaderBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let name = fourcc_to_string(self.name);
-        // Dimensions are 16.16 fixed-point.
-        let base = u16::MAX as f64 + 1.0;
-        let width = (self.width as f64) / base;
-        let height = (self.height as f64) / base;
-        let disabled = if self.enabled { "" } else { " (disabled)" };
-        write!(f, "'{}' {} bytes duration {} id {} {}x{}{}",
-               name, self.size, self.duration, self.track_id,
-               width, height, disabled)
-    }
-}
-
-impl fmt::Display for EditListBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut entries = String::new();
-        for entry in &self.edits {
-            entries.push_str(&format!("\n  duration {} time {} rate {}/{}",
-                                      entry.segment_duration, entry.media_time,
-                                      entry.media_rate_integer, entry.media_rate_fraction));
-        }
-        write!(f, "'{}' {} bytes {}", fourcc_to_string(self.name), self.size, entries)
-    }
-}
-
-impl fmt::Display for MediaHeaderBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "'{}' {} bytes timescale {} duration {}",
-               fourcc_to_string(self.name), self.size, self.timescale, self.duration)
-    }
-}
-
-impl fmt::Display for ChunkOffsetBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut entries = String::new();
-        for entry in &self.offsets {
-            entries.push_str(&format!("\n  offset {}", entry));
-        }
-        write!(f, "'{}' {} bytes {}",
-               fourcc_to_string(self.name), self.size, entries)
-    }
-}
-
-impl fmt::Display for SyncSampleBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut entries = String::new();
-        for entry in &self.samples {
-            entries.push_str(&format!("\n  sample {}", entry));
-        }
-        write!(f, "'{}' {} bytes {}",
-               fourcc_to_string(self.name), self.size, entries)
-    }
-}
-
-impl fmt::Display for SampleToChunkBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut entries = String::new();
-        for entry in &self.samples {
-            entries.push_str(&format!("\n  sample chunk {} {} {}",
-                                      entry.first_chunk, entry.samples_per_chunk, entry.sample_description_index));
-        }
-        write!(f, "'{}' {} bytes {}",
-               fourcc_to_string(self.name), self.size, entries)
-    }
-}
-
-impl fmt::Display for SampleSizeBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut entries = String::new();
-        for entry in &self.sample_sizes {
-            entries.push_str(&format!("\n  sample size {}", entry));
-        }
-        write!(f, "'{}' {} bytes sample size {} {}",
-               fourcc_to_string(self.name), self.size, self.sample_size, entries)
-    }
-}
-
-impl fmt::Display for TimeToSampleBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut entries = String::new();
-        for entry in &self.samples {
-            entries.push_str(&format!("\n  sample count {} delta {}", entry.sample_count, entry.sample_delta));
-        }
-        write!(f, "'{}' {} bytes sample {}",
-               fourcc_to_string(self.name), self.size, entries)
-    }
-}
-
-impl fmt::Display for HandlerBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "'{}' {} bytes handler_type '{}'",
-               fourcc_to_string(self.name), self.size, fourcc_to_string(self.handler_type))
-    }
-}
-
-impl fmt::Display for SampleDescriptionBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "'{}' {} bytes descriptions {}",
-               fourcc_to_string(self.name), self.size, self.descriptions.len())
-    }
-}
-
 #[test]
 fn test_read_box_header() {
     use std::io::Cursor;
     use std::io::Write;
     let mut test: Vec<u8> = vec![0, 0, 0, 8]; // minimal box length
     write!(&mut test, "test").unwrap(); // box type
     let mut stream = Cursor::new(test);
     let parsed = read_box_header(&mut stream).unwrap();
-    assert_eq!(parsed.name, 1952805748);
+    assert_eq!(parsed.name, FourCC(1952805748));
     assert_eq!(parsed.size, 8);
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
 
 #[test]
 fn test_read_box_header_long() {
     use std::io::Cursor;
     let mut test: Vec<u8> = vec![0, 0, 0, 1]; // long box extension code
     test.extend("long".to_string().into_bytes()); // box type
     test.extend(vec![0, 0, 0, 0, 0, 0, 16, 0]); // 64 bit size
     // Skip generating box content.
     let mut stream = Cursor::new(test);
     let parsed = read_box_header(&mut stream).unwrap();
-    assert_eq!(parsed.name, 1819242087);
+    assert_eq!(parsed.name, FourCC(1819242087));
     assert_eq!(parsed.size, 4096);
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
 
 #[test]
 fn test_read_ftyp() {
     use std::io::Cursor;
     use std::io::Write;
     let mut test: Vec<u8> = vec![0, 0, 0, 24]; // size
     write!(&mut test, "ftyp").unwrap(); // type
@@ -1005,24 +875,24 @@ fn test_read_ftyp() {
     test.extend(vec![0, 0, 0, 0]);      // minor version
     write!(&mut test, "isom").unwrap(); // compatible brands...
     write!(&mut test, "mp42").unwrap();
     assert_eq!(test.len(), 24);
 
     let mut stream = Cursor::new(test);
     let header = read_box_header(&mut stream).unwrap();
     let parsed = read_ftyp(&mut stream, &header).unwrap();
-    assert_eq!(parsed.name, 1718909296);
+    assert_eq!(parsed.name, FourCC(1718909296));
     assert_eq!(parsed.size, 24);
-    assert_eq!(parsed.major_brand, 1836069938);
+    assert_eq!(parsed.major_brand, FourCC(1836069938));
     assert_eq!(parsed.minor_version, 0);
     assert_eq!(parsed.compatible_brands.len(), 2);
-    assert_eq!(parsed.compatible_brands[0], 1769172845);
+    assert_eq!(parsed.compatible_brands[0], FourCC(1769172845));
     assert_eq!(fourcc_to_string(parsed.compatible_brands[1]), "mp42");
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
 
 #[test]
 fn test_read_elst_v0() {
     use std::io::Cursor;
     use std::io::Write;
     let mut test: Vec<u8> = vec![0, 0, 0, 28]; // size
     write!(&mut test, "elst").unwrap(); // type
@@ -1032,24 +902,24 @@ fn test_read_elst_v0() {
                      5, 6, 7, 8,
                      9, 10,
                      11, 12]);
     assert_eq!(test.len(), 28);
 
     let mut stream = Cursor::new(test);
     let header = read_box_header(&mut stream).unwrap();
     let parsed = read_elst(&mut stream, &header).unwrap();
-    assert_eq!(parsed.name, 1701606260);
+    assert_eq!(parsed.name, FourCC(1701606260));
     assert_eq!(parsed.size, 28);
     assert_eq!(parsed.edits.len(), 1);
     assert_eq!(parsed.edits[0].segment_duration, 16909060);
     assert_eq!(parsed.edits[0].media_time, 84281096);
     assert_eq!(parsed.edits[0].media_rate_integer, 2314);
     assert_eq!(parsed.edits[0].media_rate_fraction, 2828);
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
 
 #[test]
 fn test_read_elst_v1() {
     use std::io::Cursor;
     use std::io::Write;
     let mut test: Vec<u8> = vec![0, 0, 0, 56]; // size
     write!(&mut test, "elst").unwrap(); // type
@@ -1063,24 +933,24 @@ fn test_read_elst_v1() {
                      5, 6, 7, 8, 5, 6, 7, 8,
                      9, 10,
                      11, 12]);
     assert_eq!(test.len(), 56);
 
     let mut stream = Cursor::new(test);
     let header = read_box_header(&mut stream).unwrap();
     let parsed = read_elst(&mut stream, &header).unwrap();
-    assert_eq!(parsed.name, 1701606260);
+    assert_eq!(parsed.name, FourCC(1701606260));
     assert_eq!(parsed.size, 56);
     assert_eq!(parsed.edits.len(), 2);
     assert_eq!(parsed.edits[1].segment_duration, 72623859723010820);
     assert_eq!(parsed.edits[1].media_time, 361984551075317512);
     assert_eq!(parsed.edits[1].media_rate_integer, 2314);
     assert_eq!(parsed.edits[1].media_rate_fraction, 2828);
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
 
 #[test]
 fn test_read_mdhd_v0() {
     use std::io::Cursor;
     use std::io::Write;
     let mut test: Vec<u8> = vec![0, 0, 0, 32]; // size
     write!(&mut test, "mdhd").unwrap(); // type
@@ -1090,21 +960,21 @@ fn test_read_mdhd_v0() {
                      1, 2, 3, 4,
                      5, 6, 7, 8,
                      0, 0, 0, 0]);
     assert_eq!(test.len(), 32);
 
     let mut stream = Cursor::new(test);
     let header = read_box_header(&mut stream).unwrap();
     let parsed = read_mdhd(&mut stream, &header).unwrap();
-    assert_eq!(parsed.name, 1835296868);
+    assert_eq!(parsed.name, FourCC(1835296868));
     assert_eq!(parsed.size, 32);
     assert_eq!(parsed.timescale, 16909060);
     assert_eq!(parsed.duration, 84281096);
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
 
 #[test]
 fn test_read_mdhd_v1() {
     use std::io::Cursor;
     use std::io::Write;
     let mut test: Vec<u8> = vec![0, 0, 0, 44]; // size
     write!(&mut test, "mdhd").unwrap(); // type
@@ -1114,14 +984,14 @@ fn test_read_mdhd_v1() {
                      1, 2, 3, 4,
                      5, 6, 7, 8, 5, 6, 7, 8,
                      0, 0, 0, 0]);
     assert_eq!(test.len(), 44);
 
     let mut stream = Cursor::new(test);
     let header = read_box_header(&mut stream).unwrap();
     let parsed = read_mdhd(&mut stream, &header).unwrap();
-    assert_eq!(parsed.name, 1835296868);
+    assert_eq!(parsed.name, FourCC(1835296868));
     assert_eq!(parsed.size, 44);
     assert_eq!(parsed.timescale, 16909060);
     assert_eq!(parsed.duration, 361984551075317512);
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
new file mode 100644
--- /dev/null
+++ b/media/libstagefright/binding/capi.rs
@@ -0,0 +1,103 @@
+// C API for mp4parse module.
+// Parses ISO Base Media Format aka video/mp4 streams.
+
+// 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 https://mozilla.org/MPL/2.0/.
+
+use std;
+use std::io::Cursor;
+use byteorder;
+
+// Symbols we need from our rust api.
+use MediaContext;
+use read_box;
+
+/// Allocate an opaque rust-side parser context.
+#[no_mangle]
+pub extern "C" fn mp4parse_new() -> *mut MediaContext {
+    let context = Box::new(MediaContext::new());
+    unsafe {
+        // transmute is unsafe, but context is always valid.
+        std::mem::transmute(context)
+    }
+}
+
+/// Free a rust-side parser context.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_free(context: *mut MediaContext) {
+    assert!(!context.is_null());
+    let _: Box<MediaContext> = std::mem::transmute(context);
+}
+
+/// Feed a buffer through read_box(), returning the number of detected tracks.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_read(context: *mut MediaContext, buffer: *const u8, size: usize) -> i32 {
+    // Validate arguments from C.
+    if context.is_null() {
+        return -1;
+    }
+    if buffer.is_null() || size < 8 {
+        return -1;
+    }
+
+    let mut context: &mut MediaContext = &mut *context;
+
+    // Wrap the buffer we've been give in a slice.
+    let b = std::slice::from_raw_parts(buffer, size);
+    let mut c = Cursor::new(b);
+
+    // Parse in a subthread to catch any panics.
+    let task = std::thread::spawn(move || {
+        loop {
+            match read_box(&mut c, &mut context) {
+                Ok(_) => {},
+                Err(byteorder::Error::UnexpectedEOF) => { break },
+                Err(e) => { panic!(e); },
+            }
+        }
+        // Make sure the track count fits in an i32 so we can use
+        // negative values for failure.
+        assert!(context.tracks.len() < std::i32::MAX as usize);
+        context.tracks.len() as i32
+    });
+    task.join().unwrap_or(-1)
+}
+
+#[test]
+fn new_context() {
+    let context = mp4parse_new();
+    assert!(!context.is_null());
+    unsafe { mp4parse_free(context); }
+}
+
+#[test]
+#[should_panic(expected = "assertion failed")]
+fn free_null_context() {
+    unsafe { mp4parse_free(std::ptr::null_mut()); }
+}
+
+#[test]
+fn arg_validation() {
+    let null_buffer = std::ptr::null();
+    let null_context = std::ptr::null_mut();
+
+    let context = mp4parse_new();
+    assert!(!context.is_null());
+
+    let buffer = vec![0u8; 8];
+
+    unsafe {
+        assert_eq!(-1, mp4parse_read(null_context, null_buffer, 0));
+        assert_eq!(-1, mp4parse_read(context, null_buffer, 0));
+    }
+
+    for size in (0..buffer.len()) {
+        println!("testing buffer length {}", size);
+        unsafe {
+            assert_eq!(-1, mp4parse_read(context, buffer.as_ptr(), size));
+        }
+    }
+
+    unsafe { mp4parse_free(context); }
+}
new file mode 100644
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4parse.h
@@ -0,0 +1,23 @@
+// 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 https://mozilla.org/MPL/2.0/.
+
+#ifndef _MP4PARSE_RUST_H
+#define _MP4PARSE_RUST_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct mp4parse_state;
+
+struct mp4parse_state* mp4parse_new(void);
+void mp4parse_free(struct mp4parse_state* state);
+
+int32_t mp4parse_read(struct mp4parse_state* state, uint8_t *buffer, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
--- a/media/libstagefright/gtest/TestMP4Rust.cpp
+++ b/media/libstagefright/gtest/TestMP4Rust.cpp
@@ -1,50 +1,68 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
 #include "mp4_demuxer/MP4Metadata.h"
+#include "mp4parse.h"
 
 #include <stdint.h>
 #include <stdio.h>
 #include <vector>
 
-extern "C" int32_t read_box_from_buffer(uint8_t *buffer, size_t size);
-
 using namespace mp4_demuxer;
 using namespace mozilla;
 
 TEST(rust, MP4MetadataEmpty)
 {
   int32_t rv;
-  rv = read_box_from_buffer(nullptr, 0);
+
+  mp4parse_state* context = mp4parse_new();
+  ASSERT_NE(context, nullptr);
+
+  rv = mp4parse_read(nullptr, nullptr, 0);
+  EXPECT_EQ(rv, -1);
+  rv = mp4parse_read(context, nullptr, 0);
   EXPECT_EQ(rv, -1);
 
   size_t len = 4097;
-  rv = read_box_from_buffer(nullptr, len);
+  rv = mp4parse_read(nullptr, nullptr, len);
+  EXPECT_EQ(rv, -1);
+  rv = mp4parse_read(context, nullptr, len);
   EXPECT_EQ(rv, -1);
 
   std::vector<uint8_t> buf;
-  rv = read_box_from_buffer(buf.data(), buf.size());
+  rv = mp4parse_read(nullptr, buf.data(), buf.size());
+  EXPECT_EQ(rv, -1);
+  rv = mp4parse_read(context, buf.data(), buf.size());
   EXPECT_EQ(rv, -1);
 
   buf.reserve(len);
-  rv = read_box_from_buffer(buf.data(), buf.size());
+  rv = mp4parse_read(nullptr, buf.data(), buf.size());
   EXPECT_EQ(rv, -1);
+  rv = mp4parse_read(context, buf.data(), buf.size());
+  EXPECT_EQ(rv, -1);
+
+  mp4parse_free(context);
 }
 
 TEST(rust, MP4Metadata)
 {
   FILE* f = fopen("street.mp4", "rb");
   ASSERT_TRUE(f != nullptr);
 
   size_t len = 4096;
   std::vector<uint8_t> buf(len);
   size_t read = fread(buf.data(), sizeof(decltype(buf)::value_type), buf.size(), f);
   buf.resize(read);
   fclose(f);
 
-  int32_t rv = read_box_from_buffer(buf.data(), buf.size());
+  mp4parse_state* context = mp4parse_new();
+  ASSERT_NE(context, nullptr);
+
+  int32_t rv = mp4parse_read(context, buf.data(), buf.size());
   EXPECT_EQ(rv, 2);
+
+  mp4parse_free(context);
 }
--- a/media/libstagefright/gtest/moz.build
+++ b/media/libstagefright/gtest/moz.build
@@ -18,13 +18,16 @@ TEST_HARNESS_FILES.gtest += [
     'test_case_1204580.mp4',
 ]
 
 if CONFIG['MOZ_RUST']:
     UNIFIED_SOURCES += ['TestMP4Rust.cpp',]
     TEST_HARNESS_FILES.gtest += [
         '../../../dom/media/test/street.mp4',
     ]
+    LOCAL_INCLUDES += [
+        '../binding/include',
+    ]
 
 FINAL_LIBRARY = 'xul-gtest'
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wshadow']
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4808,16 +4808,18 @@ pref("urlclassifier.disallow_completions
 // The table and update/gethash URLs for Safebrowsing phishing and malware
 // checks.
 pref("urlclassifier.trackingTable", "test-track-simple,mozstd-track-digest256");
 pref("urlclassifier.trackingWhitelistTable", "test-trackwhite-simple,mozstd-trackwhite-digest256");
 
 pref("browser.safebrowsing.provider.mozilla.lists", "mozstd-track-digest256,mozstd-trackwhite-digest256,mozfull-track-digest256");
 pref("browser.safebrowsing.provider.mozilla.updateURL", "https://shavar.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
 pref("browser.safebrowsing.provider.mozilla.gethashURL", "https://shavar.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
+// Set to a date in the past to force immediate download in new profiles.
+pref("browser.safebrowsing.provider.mozilla.nextupdatetime", "1");
 // Block lists for tracking protection. The name values will be used as the keys
 // to lookup the localized name in preferences.properties.
 pref("browser.safebrowsing.provider.mozilla.lists.mozstd.name", "mozstdName");
 pref("browser.safebrowsing.provider.mozilla.lists.mozstd.description", "mozstdDesc");
 pref("browser.safebrowsing.provider.mozilla.lists.mozfull.name", "mozfullName");
 pref("browser.safebrowsing.provider.mozilla.lists.mozfull.description", "mozfullDesc");
 
 // Turn off Spatial navigation by default.
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -78,16 +78,17 @@ nsHttpConnection::nsHttpConnection()
     , mUsingSpdyVersion(0)
     , mPriority(nsISupportsPriority::PRIORITY_NORMAL)
     , mReportedSpdy(false)
     , mEverUsedSpdy(false)
     , mLastHttpResponseVersion(NS_HTTP_VERSION_1_1)
     , mTransactionCaps(0)
     , mResponseTimeoutEnabled(false)
     , mTCPKeepaliveConfig(kTCPKeepaliveDisabled)
+    , mForceSendPending(false)
 {
     LOG(("Creating nsHttpConnection @%p\n", this));
 
     // the default timeout is for when this connection has not yet processed a
     // transaction
     static const PRIntervalTime k5Sec = PR_SecondsToInterval(5);
     mIdleTimeout =
         (k5Sec < gHttpHandler->IdleTimeout()) ? k5Sec : gHttpHandler->IdleTimeout();
@@ -108,16 +109,20 @@ nsHttpConnection::~nsHttpConnection()
         uint32_t totalKBRead = static_cast<uint32_t>(mTotalBytesRead >> 10);
         LOG(("nsHttpConnection %p read %dkb on connection spdy=%d\n",
              this, totalKBRead, mEverUsedSpdy));
         Telemetry::Accumulate(mEverUsedSpdy ?
                               Telemetry::SPDY_KBREAD_PER_CONN :
                               Telemetry::HTTP_KBREAD_PER_CONN,
                               totalKBRead);
     }
+    if (mForceSendTimer) {
+        mForceSendTimer->Cancel();
+        mForceSendTimer = nullptr;
+    }
 }
 
 nsresult
 nsHttpConnection::Init(nsHttpConnectionInfo *info,
                        uint16_t maxHangTime,
                        nsISocketTransport *transport,
                        nsIAsyncInputStream *instream,
                        nsIAsyncOutputStream *outstream,
@@ -564,16 +569,20 @@ nsHttpConnection::Close(nsresult reason)
 
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
     // Ensure TCP keepalive timer is stopped.
     if (mTCPKeepaliveTransitionTimer) {
         mTCPKeepaliveTransitionTimer->Cancel();
         mTCPKeepaliveTransitionTimer = nullptr;
     }
+    if (mForceSendTimer) {
+        mForceSendTimer->Cancel();
+        mForceSendTimer = nullptr;
+    }
 
     if (NS_FAILED(reason)) {
         if (mIdleMonitoring)
             EndIdleMonitoring();
 
         mTLSFilter = nullptr;
 
         // The connection and security errors clear out alt-svc mappings
@@ -1335,64 +1344,98 @@ nsHttpConnection::ResumeRecv()
     if (mSocketIn)
         return mSocketIn->AsyncWait(this, 0, 0, nullptr);
 
     NS_NOTREACHED("no socket input stream");
     return NS_ERROR_UNEXPECTED;
 }
 
 
-class nsHttpConnectionForceIO : public nsRunnable
+class HttpConnectionForceIO : public nsRunnable
 {
 public:
-  nsHttpConnectionForceIO(nsHttpConnection *aConn, bool doRecv)
+  HttpConnectionForceIO(nsHttpConnection *aConn, bool doRecv)
      : mConn(aConn)
      , mDoRecv(doRecv)
     {}
 
     NS_IMETHOD Run()
     {
         MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
         if (mDoRecv) {
             if (!mConn->mSocketIn)
                 return NS_OK;
             return mConn->OnInputStreamReady(mConn->mSocketIn);
         }
-        if (!mConn->mSocketOut)
+
+        MOZ_ASSERT(mConn->mForceSendPending);
+        mConn->mForceSendPending = false;
+        if (!mConn->mSocketOut) {
             return NS_OK;
+        }
         return mConn->OnOutputStreamReady(mConn->mSocketOut);
     }
 private:
     RefPtr<nsHttpConnection> mConn;
     bool mDoRecv;
 };
 
+void
+nsHttpConnection::ForceSendIO(nsITimer *aTimer, void *aClosure)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    nsHttpConnection *self = static_cast<nsHttpConnection *>(aClosure);
+    MOZ_ASSERT(aTimer == self->mForceSendTimer);
+    self->mForceSendTimer = nullptr;
+    NS_DispatchToCurrentThread(new HttpConnectionForceIO(self, false));
+}
+
+nsresult
+nsHttpConnection::MaybeForceSendIO()
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    // due to bug 1213084 sometimes real I/O events do not get serviced when
+    // NSPR derived I/O events are ready and this can cause a deadlock with
+    // https over https proxying. Normally we would expect the write callback to
+    // be invoked before this timer goes off, but set it at the old windows
+    // tick interval (kForceDelay) as a backup for those circumstances.
+    static const uint32_t kForceDelay = 17; //ms
+
+    if (mForceSendPending) {
+        return NS_OK;
+    }
+    MOZ_ASSERT(!mForceSendTimer);
+    mForceSendPending = true;
+    mForceSendTimer = do_CreateInstance("@mozilla.org/timer;1");
+    return mForceSendTimer->InitWithFuncCallback(
+        nsHttpConnection::ForceSendIO, this, kForceDelay, nsITimer::TYPE_ONE_SHOT);
+}
+
 // trigger an asynchronous read
 nsresult
 nsHttpConnection::ForceRecv()
 {
     LOG(("nsHttpConnection::ForceRecv [this=%p]\n", this));
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
-    return NS_DispatchToCurrentThread(new nsHttpConnectionForceIO(this, true));
+    return NS_DispatchToCurrentThread(new HttpConnectionForceIO(this, true));
 }
 
 // trigger an asynchronous write
 nsresult
 nsHttpConnection::ForceSend()
 {
     LOG(("nsHttpConnection::ForceSend [this=%p]\n", this));
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
     if (mTLSFilter) {
         return mTLSFilter->NudgeTunnel(this);
     }
-
-    return NS_DispatchToCurrentThread(new nsHttpConnectionForceIO(this, false));
+    return MaybeForceSendIO();
 }
 
 void
 nsHttpConnection::BeginIdleMonitoring()
 {
     LOG(("nsHttpConnection::BeginIdleMonitoring [this=%p]\n", this));
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     MOZ_ASSERT(!mTransaction, "BeginIdleMonitoring() while active");
@@ -2047,17 +2090,16 @@ nsHttpConnection::OnInputStreamReady(nsI
 // nsHttpConnection::nsIOutputStreamCallback
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpConnection::OnOutputStreamReady(nsIAsyncOutputStream *out)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     MOZ_ASSERT(out == mSocketOut, "unexpected socket");
-
     // if the transaction was dropped...
     if (!mTransaction) {
         LOG(("  no transaction; ignoring event\n"));
         return NS_OK;
     }
 
     nsresult rv = OnSocketWritable();
     if (NS_FAILED(rv))
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -137,17 +137,17 @@ public:
     bool     IsReused();
     void     SetIsReusedAfter(uint32_t afterMilliseconds);
     nsresult PushBack(const char *data, uint32_t length);
     nsresult ResumeSend();
     nsresult ResumeRecv();
     int64_t  MaxBytesRead() {return mMaxBytesRead;}
     uint8_t GetLastHttpResponseVersion() { return mLastHttpResponseVersion; }
 
-    friend class nsHttpConnectionForceIO;
+    friend class HttpConnectionForceIO;
     nsresult ForceSend();
     nsresult ForceRecv();
 
     static NS_METHOD ReadFromStream(nsIInputStream *, void *, const char *,
                                     uint32_t, uint32_t, uint32_t *);
 
     // When a persistent connection is in the connection manager idle
     // connection pool, the nsHttpConnection still reads errors and hangups
@@ -344,14 +344,21 @@ private:
     // The capabailities associated with the most recent transaction
     uint32_t                        mTransactionCaps;
 
     bool                            mResponseTimeoutEnabled;
 
     // Flag to indicate connection is in inital keepalive period (fast detect).
     uint32_t                        mTCPKeepaliveConfig;
     nsCOMPtr<nsITimer>              mTCPKeepaliveTransitionTimer;
+
+private:
+    // For ForceSend()
+    static void                     ForceSendIO(nsITimer *aTimer, void *aClosure);
+    nsresult                        MaybeForceSendIO();
+    bool                            mForceSendPending;
+    nsCOMPtr<nsITimer>              mForceSendTimer;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // nsHttpConnection_h__
--- a/security/manager/ssl/tests/unit/test_weak_crypto.js
+++ b/security/manager/ssl/tests/unit/test_weak_crypto.js
@@ -98,78 +98,77 @@ function startServer(cert, rc4only) {
 function storeCertOverride(port, cert) {
   let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
                      Ci.nsICertOverrideService.ERROR_MISMATCH;
   certOverrideService.rememberValidityOverride("127.0.0.1", port, cert,
                                                overrideBits, true);
 }
 
 function startClient(port, expectedResult, options = {}) {
-  let SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
-  let SSL_ERROR_BAD_CERT_ALERT = SSL_ERROR_BASE + 17;
   let transport =
     socketTransportService.createTransport(["ssl"], 1, "127.0.0.1", port, null);
   if (options.isPrivate) {
     transport.connectionFlags |= Ci.nsISocketTransport.NO_PERMANENT_STORAGE;
   }
   let input;
   let output;
 
-  let inputDeferred = Promise.defer();
-  let outputDeferred = Promise.defer();
+  let deferred = Promise.defer();
 
   let handler = {
 
     onTransportStatus: function(transport, status) {
       if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
         output.asyncWait(handler, 0, 0, Services.tm.currentThread);
       }
     },
 
     onInputStreamReady: function(input) {
-      let errorCode = Cr.NS_OK;
       try {
         let data = NetUtil.readInputStreamToString(input, input.available());
         equal(data, "HELLO", "Echoed data received");
         input.close();
         output.close();
+        deferred.resolve();
       } catch (e) {
-        errorCode = e.result;
-      }
-      try {
-        equal(errorCode, expectedResult,
-              "Actual and expected connection result should match");
-        inputDeferred.resolve();
-      } catch (e) {
-        inputDeferred.reject(e);
+        deferred.reject(e);
       }
     },
 
     onOutputStreamReady: function(output) {
       try {
-        output.write("HELLO", 5);
+        try {
+          output.write("HELLO", 5);
+        } catch (e) {
+          if (e.result == Cr.NS_BASE_STREAM_WOULD_BLOCK) {
+            output.asyncWait(handler, 0, 0, Services.tm.currentThread);
+            return;
+          }
+          equal(e.result, expectedResult,
+                "Actual and expected connection result should match");
+          output.close();
+          deferred.resolve();
+          return;
+        }
+        equal(Cr.NS_OK, expectedResult, "Connection should succeed");
         do_print("Output to server written");
-        outputDeferred.resolve();
         input = transport.openInputStream(0, 0, 0);
         input.asyncWait(handler, 0, 0, Services.tm.currentThread);
       } catch (e) {
-        let errorCode = -1 * (e.result & 0xFFFF);
-        if (errorCode == SSL_ERROR_BAD_CERT_ALERT) {
-          do_print("Server doesn't like client cert");
-        }
-        outputDeferred.reject(e);
+        deferred.reject(e);
       }
     }
 
   };
 
   transport.setEventSink(handler, Services.tm.currentThread);
-  output = transport.openOutputStream(0, 0, 0);
+  output = transport.openOutputStream(Ci.nsITransport.OPEN_UNBUFFERED, 0, 0);
+  output.QueryInterface(Ci.nsIAsyncOutputStream);
 
-  return Promise.all([inputDeferred.promise, outputDeferred.promise]);
+  return deferred.promise;
 }
 
 function run_test() {
   Services.prefs.setBoolPref("security.tls.unrestricted_rc4_fallback", false);
   run_next_test();
 }
 
 // for sanity check
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -147,9 +147,8 @@ run-sequentially = hardcoded ports
 [test_constructX509FromBase64.js]
 [test_validity.js]
 run-sequentially = hardcoded ports
 [test_certviewer_invalid_oids.js]
 skip-if = toolkit == 'android' || buildapp == 'b2g'
 
 [test_weak_crypto.js]
 firefox-appdir = browser
-skip-if = toolkit == 'android'
--- a/testing/mozbase/mozprocess/tests/proctest.py
+++ b/testing/mozbase/mozprocess/tests/proctest.py
@@ -1,58 +1,41 @@
 import os
 import sys
 import unittest
 import psutil
 
 here = os.path.dirname(os.path.abspath(__file__))
 
 
-def check_for_process(processName):
-    """
-        Use to determine if process of the given name is still running.
-
-        Returns:
-        detected -- True if process is detected to exist, False otherwise
-        output -- if process exists, stdout of the process, [] otherwise
-    """
-    name = os.path.basename(processName)
-    process = [p.pid for p in psutil.process_iter()
-               if p.name() == name]
-
-    if process:
-        return True, process
-    return False, []
-
-
 class ProcTest(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
         cls.proclaunch = os.path.join(here, "proclaunch.py")
         cls.python = sys.executable
 
-    def determine_status(self,
-                         detected=False,
-                         output='',
-                         returncode=0,
-                         didtimeout=False,
-                         isalive=False,
-                         expectedfail=()):
+    def determine_status(self, proc, isalive=False, expectedfail=()):
         """
         Use to determine if the situation has failed.
         Parameters:
-            detected -- value from check_for_process to determine if the process is detected
-            output -- string of data from detected process, can be ''
-            returncode -- return code from process, defaults to 0
-            didtimeout -- True if process timed out, defaults to False
+            proc -- the processhandler instance
             isalive -- Use True to indicate we pass if the process exists; however, by default
                        the test will pass if the process does not exist (isalive == False)
             expectedfail -- Defaults to [], used to indicate a list of fields that are expected to fail
         """
+        returncode = proc.proc.returncode
+        didtimeout = proc.didTimeout
+        detected = psutil.pid_exists(proc.pid)
+        output = ''
+        # ProcessHandler has output when store_output is set to True in the constructor
+        # (this is the default)
+        if getattr(proc, 'output'):
+            output = proc.output
+
         if 'returncode' in expectedfail:
             self.assertTrue(returncode, "Detected an unexpected return code of: %s" % returncode)
         elif isalive:
             self.assertEqual(returncode, None, "Detected not None return code of: %s" % returncode)
         else:
             self.assertNotEqual(returncode, None, "Detected unexpected None return code of")
 
         if 'didtimeout' in expectedfail:
--- a/testing/mozbase/mozprocess/tests/test_mozprocess.py
+++ b/testing/mozbase/mozprocess/tests/test_mozprocess.py
@@ -3,16 +3,17 @@
 # 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/.
 
 import os
 import subprocess
 import sys
 import unittest
+import proctest
 from mozprocess import processhandler
 
 here = os.path.dirname(os.path.abspath(__file__))
 
 def make_proclaunch(aDir):
     """
         Makes the proclaunch executable.
         Params:
@@ -44,55 +45,18 @@ def make_proclaunch(aDir):
             print "stderr:\n%s" % stderr
             raise subprocess.CalledProcessError(process.returncode, command, stdout)
 
     # ensure the launcher now exists
     if not os.path.exists(exepath):
         raise AssertionError("proclaunch executable '%s' does not exist (sys.platform=%s)" % (exepath, sys.platform))
     return exepath
 
-def check_for_process(processName):
-    """
-        Use to determine if process of the given name is still running.
 
-        Returns:
-        detected -- True if process is detected to exist, False otherwise
-        output -- if process exists, stdout of the process, '' otherwise
-    """
-    # TODO: replace with
-    # https://github.com/mozilla/mozbase/blob/master/mozprocess/mozprocess/pid.py
-    # which should be augmented from talos
-    # see https://bugzilla.mozilla.org/show_bug.cgi?id=705864
-    output = ''
-    if sys.platform == "win32":
-        # On windows we use tasklist
-        p1 = subprocess.Popen(["tasklist"], stdout=subprocess.PIPE)
-        output = p1.communicate()[0]
-        detected = False
-        for line in output.splitlines():
-            if processName in line:
-                detected = True
-                break
-    else:
-        p1 = subprocess.Popen(["ps", "-ef"], stdout=subprocess.PIPE)
-        p2 = subprocess.Popen(["grep", processName], stdin=p1.stdout, stdout=subprocess.PIPE)
-        p1.stdout.close()
-        output = p2.communicate()[0]
-        detected = False
-        for line in output.splitlines():
-            if "grep %s" % processName in line:
-                continue
-            elif processName in line and not 'defunct' in line:
-                detected = True
-                break
-
-    return detected, output
-
-
-class ProcTest(unittest.TestCase):
+class ProcTest(proctest.ProcTest):
 
     # whether to remove created files on exit
     cleanup = os.environ.get('CLEANUP', 'true').lower() in ('1', 'true')
 
     @classmethod
     def setUpClass(cls):
         cls.proclaunch = make_proclaunch(here)
 
@@ -123,21 +87,17 @@ class ProcTest(unittest.TestCase):
     def test_process_normal_finish(self):
         """Process is started, runs to completion while we wait for it"""
 
         p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"],
                                           cwd=here)
         p.run()
         p.wait()
 
-        detected, output = check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout)
+        self.determine_status(p)
 
     def test_commandline_no_args(self):
         """Command line is reported correctly when no arguments are specified"""
         p = processhandler.ProcessHandler(self.proclaunch, cwd=here)
         self.assertEqual(p.commandline, self.proclaunch)
 
     def test_commandline_overspecified(self):
         """Command line raises an exception when the arguments are specified ambiguously"""
@@ -180,38 +140,28 @@ class ProcTest(unittest.TestCase):
         """Process is started runs to completion while we wait indefinitely"""
 
         p = processhandler.ProcessHandler([self.proclaunch,
                                           "process_waittimeout_10s.ini"],
                                           cwd=here)
         p.run()
         p.wait()
 
-        detected, output = check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout)
+        self.determine_status(p)
 
     def test_process_timeout(self):
         """ Process is started, runs but we time out waiting on it
             to complete
         """
         p = processhandler.ProcessHandler([self.proclaunch, "process_waittimeout.ini"],
                                           cwd=here)
         p.run(timeout=10)
         p.wait()
 
-        detected, output = check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              False,
-                              ['returncode', 'didtimeout'])
+        self.determine_status(p, False, ['returncode', 'didtimeout'])
 
     def test_process_timeout_no_kill(self):
         """ Process is started, runs but we time out waiting on it
             to complete. Process should not be killed.
         """
         p = None
         def timeout_handler():
             self.assertEqual(p.proc.poll(), None)
@@ -219,120 +169,62 @@ class ProcTest(unittest.TestCase):
         p = processhandler.ProcessHandler([self.proclaunch, "process_waittimeout.ini"],
                                           cwd=here,
                                           onTimeout=(timeout_handler,),
                                           kill_on_timeout=False)
         p.run(timeout=1)
         p.wait()
         self.assertTrue(p.didTimeout)
 
-        detected, output = check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              False,
-                              ['returncode', 'didtimeout'])
+        self.determine_status(p, False, ['returncode', 'didtimeout'])
 
     def test_process_waittimeout(self):
         """
         Process is started, then wait is called and times out.
         Process is still running and didn't timeout
         """
         p = processhandler.ProcessHandler([self.proclaunch,
                                           "process_waittimeout_10s.ini"],
                                           cwd=here)
 
         p.run()
         p.wait(timeout=5)
 
-        detected, output = check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              True,
-                              ())
+        self.determine_status(p, True, ())
 
     def test_process_waitnotimeout(self):
         """ Process is started, runs to completion before our wait times out
         """
         p = processhandler.ProcessHandler([self.proclaunch,
                                           "process_waittimeout_10s.ini"],
                                           cwd=here)
         p.run(timeout=30)
         p.wait()
 
-        detected, output = check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout)
+        self.determine_status(p)
 
     def test_process_kill(self):
         """Process is started, we kill it"""
 
         p = processhandler.ProcessHandler([self.proclaunch, "process_normal_finish.ini"],
                                           cwd=here)
         p.run()
         p.kill()
 
-        detected, output = check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout)
+        self.determine_status(p)
 
     def test_process_output_twice(self):
         """
         Process is started, then processOutput is called a second time explicitly
         """
         p = processhandler.ProcessHandler([self.proclaunch,
                                           "process_waittimeout_10s.ini"],
                                           cwd=here)
 
         p.run()
         p.processOutput(timeout=5)
         p.wait()
 
-        detected, output = check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              False,
-                              ())
+        self.determine_status(p, False, ())
 
-    def determine_status(self,
-                         detected=False,
-                         output='',
-                         returncode=0,
-                         didtimeout=False,
-                         isalive=False,
-                         expectedfail=()):
-        """
-        Use to determine if the situation has failed.
-        Parameters:
-            detected -- value from check_for_process to determine if the process is detected
-            output -- string of data from detected process, can be ''
-            returncode -- return code from process, defaults to 0
-            didtimeout -- True if process timed out, defaults to False
-            isalive -- Use True to indicate we pass if the process exists; however, by default
-                       the test will pass if the process does not exist (isalive == False)
-            expectedfail -- Defaults to [], used to indicate a list of fields that are expected to fail
-        """
-        if 'returncode' in expectedfail:
-            self.assertTrue(returncode, "Detected an unexpected return code of: %s" % returncode)
-        elif not isalive:
-            self.assertTrue(returncode == 0, "Detected non-zero return code of: %d" % returncode)
-
-        if 'didtimeout' in expectedfail:
-            self.assertTrue(didtimeout, "Detected that process didn't time out")
-        else:
-            self.assertTrue(not didtimeout, "Detected that process timed out")
-
-        if isalive:
-            self.assertTrue(detected, "Detected process is not running, process output: %s" % output)
-        else:
-            self.assertTrue(not detected, "Detected process is still running, process output: %s" % output)
 
 if __name__ == '__main__':
     unittest.main()
--- a/testing/mozbase/mozprocess/tests/test_mozprocess_kill.py
+++ b/testing/mozbase/mozprocess/tests/test_mozprocess_kill.py
@@ -21,70 +21,50 @@ class ProcTestKill(proctest.ProcTest):
     def test_process_kill(self):
         """Process is started, we kill it"""
 
         p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_normal_finish_python.ini"],
                                           cwd=here)
         p.run()
         p.kill()
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              expectedfail=('returncode',))
+        self.determine_status(p, expectedfail=('returncode',))
 
     def test_process_kill_deep(self):
         """Process is started, we kill it, we use a deep process tree"""
 
         p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_normal_deep_python.ini"],
                                           cwd=here)
         p.run()
         p.kill()
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              expectedfail=('returncode',))
+        self.determine_status(p, expectedfail=('returncode',))
 
     def test_process_kill_deep_wait(self):
         """Process is started, we use a deep process tree, we let it spawn
            for a bit, we kill it"""
 
         p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_normal_deep_python.ini"],
                                           cwd=here)
         p.run()
         # Let the tree spawn a bit, before attempting to kill
         time.sleep(3)
         p.kill()
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              expectedfail=('returncode',))
+        self.determine_status(p, expectedfail=('returncode',))
 
     def test_process_kill_broad(self):
         """Process is started, we kill it, we use a broad process tree"""
 
         p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_normal_broad_python.ini"],
                                           cwd=here)
         p.run()
         p.kill()
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              expectedfail=('returncode',))
+        self.determine_status(p, expectedfail=('returncode',))
 
     @unittest.skipUnless(processhandler.isPosix, "posix only")
     def test_process_kill_with_sigterm(self):
         script = os.path.join(here, 'infinite_loop.py')
         p = processhandler.ProcessHandler([self.python, script])
 
         p.run()
         p.kill()
--- a/testing/mozbase/mozprocess/tests/test_mozprocess_kill_broad_wait.py
+++ b/testing/mozbase/mozprocess/tests/test_mozprocess_kill_broad_wait.py
@@ -20,17 +20,12 @@ class ProcTestKill(proctest.ProcTest):
 
         p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_normal_broad_python.ini"],
                                           cwd=here)
         p.run()
         # Let the tree spawn a bit, before attempting to kill
         time.sleep(3)
         p.kill()
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              expectedfail=('returncode',))
+        self.determine_status(p, expectedfail=('returncode',))
 
 if __name__ == '__main__':
     unittest.main()
--- a/testing/mozbase/mozprocess/tests/test_mozprocess_misc.py
+++ b/testing/mozbase/mozprocess/tests/test_mozprocess_misc.py
@@ -17,18 +17,12 @@ class ProcTestMisc(proctest.ProcTest):
         p = processhandler.ProcessHandler([self.python, self.proclaunch,
                                           "process_waittimeout_10s_python.ini"],
                                           cwd=here)
 
         p.run()
         p.processOutput(timeout=5)
         p.wait()
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              False,
-                              ())
+        self.determine_status(p, False, ())
 
 if __name__ == '__main__':
     unittest.main()
--- a/testing/mozbase/mozprocess/tests/test_mozprocess_output.py
+++ b/testing/mozbase/mozprocess/tests/test_mozprocess_output.py
@@ -17,23 +17,17 @@ class ProcTestOutput(proctest.ProcTest):
         """
         p = processhandler.ProcessHandler([self.python, "procnonewline.py"],
                                           cwd=here)
 
         p.run()
         p.processOutput(timeout=5)
         p.wait()
 
-        detected, output = proctest.check_for_process("procnonewline.py")
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              False,
-                              ())
+        self.determine_status(p, False, ())
 
     def test_stream_process_output(self):
         """
         Process output stream does not buffer
         """
         expected = '\n'.join([str(n) for n in range(0,10)])
 
         stream = io.BytesIO()
@@ -51,18 +45,12 @@ class ProcTestOutput(proctest.ProcTest):
         buf.flush()
         self.assertEquals(stream.getvalue().strip(), expected)
 
         # make sure mozprocess doesn't close the stream
         # since mozprocess didn't create it
         self.assertFalse(buf.closed)
         buf.close()
 
-        detected, output = proctest.check_for_process("proccountfive.py")
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              False,
-                              ())
+        self.determine_status(p, False, ())
 
 if __name__ == '__main__':
     unittest.main()
--- a/testing/mozbase/mozprocess/tests/test_mozprocess_poll.py
+++ b/testing/mozbase/mozprocess/tests/test_mozprocess_poll.py
@@ -29,99 +29,78 @@ class ProcTestPoll(proctest.ProcTest):
         p = processhandler.ProcessHandler([self.python, self.proclaunch,
                                           "process_normal_finish_python.ini"],
                                           cwd=here)
         p.run()
         returncode = p.poll()
 
         self.assertEqual(returncode, None)
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              returncode,
-                              p.didTimeout,
-                              True)
+        self.determine_status(p, True)
         p.kill()
 
     def test_poll_after_kill(self):
         """Process is killed, and poll() is called"""
 
         p = processhandler.ProcessHandler([self.python, self.proclaunch,
                                           "process_normal_finish_python.ini"],
                                           cwd=here)
         p.run()
         returncode = p.kill()
 
         # We killed the process, so the returncode should be < 0
         self.assertLess(returncode, 0)
         self.assertEqual(returncode, p.poll())
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              returncode,
-                              p.didTimeout)
+        self.determine_status(p)
 
     def test_poll_after_kill_no_process_group(self):
         """Process (no group) is killed, and poll() is called"""
 
         p = processhandler.ProcessHandler([self.python, self.proclaunch,
                                           "process_normal_finish_no_process_group.ini"],
                                           cwd=here,
                                           ignore_children=True
                                           )
         p.run()
         returncode = p.kill()
 
         # We killed the process, so the returncode should be < 0
         self.assertLess(returncode, 0)
         self.assertEqual(returncode, p.poll())
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              returncode,
-                              p.didTimeout)
+        self.determine_status(p)
 
     def test_poll_after_double_kill(self):
         """Process is killed twice, and poll() is called"""
 
         p = processhandler.ProcessHandler([self.python, self.proclaunch,
                                           "process_normal_finish_python.ini"],
                                           cwd=here)
         p.run()
         p.kill()
         returncode = p.kill()
 
         # We killed the process, so the returncode should be < 0
         self.assertLess(returncode, 0)
         self.assertEqual(returncode, p.poll())
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              returncode,
-                              p.didTimeout)
+        self.determine_status(p)
 
     def test_poll_after_external_kill(self):
         """Process is killed externally, and poll() is called"""
 
         p = processhandler.ProcessHandler([self.python, self.proclaunch,
                                           "process_normal_finish_python.ini"],
                                           cwd=here)
         p.run()
         os.kill(p.pid, signal.SIGTERM)
         returncode = p.wait()
 
         # We killed the process, so the returncode should be < 0
         self.assertEqual(returncode, -signal.SIGTERM)
         self.assertEqual(returncode, p.poll())
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              returncode,
-                              p.didTimeout)
+        self.determine_status(p)
 
 
 if __name__ == '__main__':
     unittest.main()
--- a/testing/mozbase/mozprocess/tests/test_mozprocess_wait.py
+++ b/testing/mozbase/mozprocess/tests/test_mozprocess_wait.py
@@ -14,110 +14,81 @@ class ProcTestWait(proctest.ProcTest):
     def test_normal_finish(self):
         """Process is started, runs to completion while we wait for it"""
 
         p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_normal_finish_python.ini"],
                                           cwd=here)
         p.run()
         p.wait()
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout)
+        self.determine_status(p)
 
     def test_wait(self):
         """Process is started runs to completion while we wait indefinitely"""
 
         p = processhandler.ProcessHandler([self.python, self.proclaunch,
                                           "process_waittimeout_10s_python.ini"],
                                           cwd=here)
         p.run()
         p.wait()
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout)
+        self.determine_status(p)
 
 
     def test_timeout(self):
         """ Process is started, runs but we time out waiting on it
             to complete
         """
         p = processhandler.ProcessHandler([self.python, self.proclaunch, "process_waittimeout_python.ini"],
                                           cwd=here)
         p.run(timeout=10)
         p.wait()
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-
         if mozinfo.isUnix:
             # process was killed, so returncode should be negative
             self.assertLess(p.proc.returncode, 0)
 
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              False,
-                              ['returncode', 'didtimeout'])
+        self.determine_status(p, False, ['returncode', 'didtimeout'])
 
     def test_waittimeout(self):
         """
         Process is started, then wait is called and times out.
         Process is still running and didn't timeout
         """
         p = processhandler.ProcessHandler([self.python, self.proclaunch,
                                           "process_waittimeout_10s_python.ini"],
                                           cwd=here)
 
         p.run()
         p.wait(timeout=5)
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout,
-                              True,
-                              ())
+        self.determine_status(p, True, ())
 
     def test_waitnotimeout(self):
         """ Process is started, runs to completion before our wait times out
         """
         p = processhandler.ProcessHandler([self.python, self.proclaunch,
                                           "process_waittimeout_10s_python.ini"],
                                           cwd=here)
         p.run(timeout=30)
         p.wait()
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              p.proc.returncode,
-                              p.didTimeout)
+        self.determine_status(p)
 
     def test_wait_twice_after_kill(self):
         """Bug 968718: Process is started and stopped. wait() twice afterward."""
         p = processhandler.ProcessHandler([self.python, self.proclaunch,
                                           "process_waittimeout_python.ini"],
                                           cwd=here)
         p.run()
         p.kill()
         returncode1 = p.wait()
         returncode2 = p.wait()
 
-        detected, output = proctest.check_for_process(self.proclaunch)
-        self.determine_status(detected,
-                              output,
-                              returncode2,
-                              p.didTimeout)
+        self.determine_status(p)
 
         self.assertLess(returncode2, 0,
                         'Negative returncode expected, got "%s"' % returncode2)
         self.assertEqual(returncode1, returncode2,
                          'Expected both returncodes of wait() to be equal')
 
 if __name__ == '__main__':
     unittest.main()
--- a/testing/mozharness/mozharness/mozilla/building/buildbase.py
+++ b/testing/mozharness/mozharness/mozilla/building/buildbase.py
@@ -1391,22 +1391,27 @@ or run without that action (ie: --no-{ac
                             property_conditions=[]):
         if not self.client_id or not self.access_token:
             self.warning('Skipping S3 file upload: No taskcluster credentials.')
             return
 
         repo = self._query_repo()
         revision = self.query_revision()
         pushinfo = self.vcs_query_pushinfo(repo, revision)
+        pushdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(pushinfo.pushdate))
 
         index = self.config.get('taskcluster_index', 'index.garbage.staging')
         fmt = {
             'index': index,
             'project': self.buildbot_config['properties']['branch'],
             'head_rev': revision,
+            'pushdate': pushdate,
+            'year': pushdate[0:4],
+            'month': pushdate[4:6],
+            'day': pushdate[6:8],
             'build_product': self.config['stage_product'],
             'build_name': self.query_build_name(),
             'build_type': self.query_build_type(),
             'locale': locale,
         }
         fmt.update(self.buildid_to_dict(self.query_buildid()))
         routes = []
         for template in templates:
--- a/testing/mozharness/scripts/desktop_l10n.py
+++ b/testing/mozharness/scripts/desktop_l10n.py
@@ -6,16 +6,17 @@
 # ***** END LICENSE BLOCK *****
 """desktop_l10n.py
 
 This script manages Desktop repacks for nightly builds.
 """
 import os
 import re
 import sys
+import time
 import shlex
 import logging
 
 import subprocess
 
 # load modules from parent dir
 sys.path.insert(1, os.path.dirname(sys.path[0]))
 
@@ -960,31 +961,36 @@ class DesktopSingleLocale(LocalesMixin, 
 
         branch = self.config['branch']
         platform = self.config['platform']
         revision = self._query_revision()
         repo = self.query_l10n_repo()
         if not repo:
             self.fatal("Unable to determine repository for querying the push info.")
         pushinfo = self.vcs_query_pushinfo(repo, revision, vcs='hgtool')
+        pushdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(pushinfo.pushdate))
 
         routes_json = os.path.join(self.query_abs_dirs()['abs_mozilla_dir'],
                                    'testing/taskcluster/routes.json')
         with open(routes_json) as f:
             contents = json.load(f)
             templates = contents['l10n']
 
         for locale, files in self.upload_files.iteritems():
             self.info("Uploading files to S3 for locale '%s': %s" % (locale, files))
             routes = []
             for template in templates:
                 fmt = {
                     'index': self.config.get('taskcluster_index', 'index.garbage.staging'),
                     'project': branch,
                     'head_rev': revision,
+                    'pushdate': pushdate,
+                    'year': pushdate[0:4],
+                    'month': pushdate[4:6],
+                    'day': pushdate[6:8],
                     'build_product': self.config['stage_product'],
                     'build_name': self.query_build_name(),
                     'build_type': self.query_build_type(),
                     'locale': locale,
                 }
                 fmt.update(self.buildid_to_dict(self._query_buildid()))
                 routes.append(template.format(**fmt))
 
--- a/testing/mozharness/scripts/mobile_l10n.py
+++ b/testing/mozharness/scripts/mobile_l10n.py
@@ -10,16 +10,17 @@ This currently supports nightly and rele
 Android.  This also creates nightly updates.
 """
 
 from copy import deepcopy
 import os
 import re
 import subprocess
 import sys
+import time
 import shlex
 
 try:
     import simplejson as json
     assert json
 except ImportError:
     import json
 
@@ -481,16 +482,17 @@ class MobileSingleLocale(MockMixin, Loca
         locales = self.query_locales()
         make = self.query_exe("make")
         upload_env = self.query_upload_env()
         cwd = dirs['abs_locales_dir']
         branch = self.config['branch']
         revision = self.query_revision()
         repo = self.query_l10n_repo()
         pushinfo = self.vcs_query_pushinfo(repo, revision, vcs='hgtool')
+        pushdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(pushinfo.pushdate))
         routes_json = os.path.join(self.query_abs_dirs()['abs_mozilla_dir'],
                                    'testing/taskcluster/routes.json')
         with open(routes_json) as f:
             contents = json.load(f)
             templates = contents['l10n']
 
         for locale in locales:
             output = self.get_output_from_command_m(
@@ -501,16 +503,20 @@ class MobileSingleLocale(MockMixin, Loca
             files = shlex.split(output)
             abs_files = [os.path.abspath(os.path.join(cwd, f)) for f in files]
 
             routes = []
             fmt = {
                 'index': self.config.get('taskcluster_index', 'index.garbage.staging'),
                 'project': branch,
                 'head_rev': revision,
+                'pushdate': pushdate,
+                'year': pushdate[0:4],
+                'month': pushdate[4:6],
+                'day': pushdate[6:8],
                 'build_product': self.config['stage_product'],
                 'build_name': self.query_build_name(),
                 'build_type': self.query_build_type(),
                 'locale': locale,
             }
             for template in templates:
                 routes.append(template.format(**fmt))
 
--- a/testing/talos/talos/config.py
+++ b/testing/talos/talos/config.py
@@ -292,24 +292,16 @@ def get_counters(config):
     if config['rss']:
         counters.add('Main_RSS')
     return counters
 
 
 def get_active_tests(config):
     activeTests = config.pop('activeTests').strip().split(':')
 
-    # temporary hack for now until we have e10s running on all tests
-    if config['e10s'] and not config['develop']:
-        for testname in ('sessionrestore',
-                         'sessionrestore_no_auto_restore'):
-            if testname in activeTests:
-                print "%s is unsupported on e10s, removing from list of " \
-                    "tests to run" % testname
-                activeTests.remove(testname)
     # ensure tests are available
     availableTests = test.test_dict()
     if not set(activeTests).issubset(availableTests):
         missing = [i for i in activeTests
                    if i not in availableTests]
         raise ConfigurationError("No definition found for test(s): %s"
                                  % missing)
     return activeTests
new file mode 100644
--- /dev/null
+++ b/testing/talos/talos/startup_test/sessionrestore/addon/SessionRestoreTalosTest.js
@@ -0,0 +1,89 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
+/* 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/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+  "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
+  "resource://gre/modules/Timer.jsm");
+
+// Observer Service topics.
+const STARTUP_TOPIC = "profile-after-change";
+const RESTORED_TOPIC = "sessionstore-windows-restored";
+
+// Process Message Manager topics.
+const MSG_REQUEST = "session-restore-test?duration";
+const MSG_PROVIDE = "session-restore-test:duration";
+
+function nsSessionRestoreTalosTest() {}
+
+nsSessionRestoreTalosTest.prototype = {
+  classID: Components.ID("{716346e5-0c45-4aa2-b601-da36f3c74bd8}"),
+
+  _xpcom_factory: XPCOMUtils.generateSingletonFactory(nsSessionRestoreTalosTest),
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsISupports
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsIObserver
+
+  observe: function DS_observe(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case STARTUP_TOPIC:
+        this.init();
+        break;
+      case RESTORED_TOPIC:
+        this.onRestored();
+        break;
+      default:
+        throw new Error(`Unknown topic ${aTopic}`);
+    }
+  },
+
+  /**
+   * Perform initialization on profile-after-change.
+   */
+  init: function() {
+    Services.obs.addObserver(this, RESTORED_TOPIC, false);
+  },
+
+  /**
+   * Session Restore is complete, hurray.
+   */
+  onRestored: function() {
+    setTimeout(function() {
+      // `sessionRestored` actually becomes available only on the next tick.
+      let startup_info = Services.startup.getStartupInfo();
+      let duration = startup_info.sessionRestored - startup_info.sessionRestoreInit;
+
+      // Broadcast startup duration information immediately, in case the talos
+      // page is already loaded.
+      Services.ppmm.broadcastAsyncMessage(MSG_PROVIDE, {duration});
+
+      // Now, in case the talos page isn't loaded yet, prepare to respond if it
+      // requestions the duration information.
+      Services.ppmm.addMessageListener(MSG_REQUEST, function listener() {
+        Services.ppmm.removeMessageListener(MSG_REQUEST, listener);
+        Services.ppmm.broadcastAsyncMessage(MSG_PROVIDE, {duration});
+      });
+    }, 0);
+  },
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Module
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsSessionRestoreTalosTest]);
new file mode 100644
--- /dev/null
+++ b/testing/talos/talos/startup_test/sessionrestore/addon/chrome.manifest
@@ -0,0 +1,8 @@
+# Register a component to be informed of startup. This component can then register
+# itself to watch sessionstore-windows-restored. Once it has observed
+# sessionstore-windows-restored, it will open the webpage with the harness.
+component {716346e5-0c45-4aa2-b601-da36f3c74bd8} SessionRestoreTalosTest.js
+contract @mozilla.org/talos/session-restore-test;1 {716346e5-0c45-4aa2-b601-da36f3c74bd8}
+category profile-after-change nsSessionRestoreTalosTest @mozilla.org/talos/session-restore-test;1
+
+content session-restore-test content/
new file mode 100644
--- /dev/null
+++ b/testing/talos/talos/startup_test/sessionrestore/addon/install.rdf
@@ -0,0 +1,20 @@
+<?xml version="1.0"?><RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"     xmlns:em="http://www.mozilla.org/2004/em-rdf#"><Description about="urn:mozilla:install-manifest">
+
+<!-- Required Items -->
+<em:id>session-restore-test@mozilla.org</em:id>
+<em:name>Session Restore Startup Performance Test</em:name>
+<em:version>1.2.0</em:version>
+
+<em:targetApplication>
+    <Description>
+        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+        <em:minVersion>1.5</em:minVersion>
+        <em:maxVersion>99.0.*</em:maxVersion>
+    </Description>
+</em:targetApplication>
+
+<!-- Optional Items -->
+<em:creator>David Rajchenbach-Teller</em:creator>
+<em:description>Bug 936630, bug 1098357. This add-on broadcasts the duration of session restore.</em:description>
+<em:homepageURL>https://bugzilla.mozilla.org/show_bug.cgi?id=1098357</em:homepageURL>
+</Description></RDF>
--- a/testing/talos/talos/startup_test/sessionrestore/main.js
+++ b/testing/talos/talos/startup_test/sessionrestore/main.js
@@ -1,54 +1,42 @@
 "use strict";
 
 var Services = Components.utils.import("resource://gre/modules/Services.jsm", {}).Services;
 
-/**
- * Display the result, send it to the harness and quit.
- */
-function finish() {
-  Profiler.pause("This test measures the time between sessionRestoreInit and sessionRestored, ignore everything around that");
-  Profiler.initFromURLQueryParams(location.search);
-  Profiler.finishStartupProfiling();
+// Process Message Manager topics.
+const MSG_REQUEST = "session-restore-test?duration";
+const MSG_PROVIDE = "session-restore-test:duration";
 
-  setTimeout(function () {
-    var startup_info = Services.startup.getStartupInfo();
-
-    var duration = startup_info.sessionRestored - startup_info.sessionRestoreInit;
+Services.cpmm.addMessageListener(MSG_PROVIDE,
+  /**
+   * Display the result, send it to the harness and quit.
+   */
+  function finish(msg) {
+    console.log(`main.js: received data on ${MSG_PROVIDE}`, msg);
+    Services.cpmm.removeMessageListener(MSG_PROVIDE, finish);
+    var duration = msg.data.duration;
 
-    // Show result on screen. Nice but not really necessary.
-    document.getElementById("sessionRestoreInit-to-sessionRestored").textContent = duration + "ms";
-
-    // Report data to Talos, if possible
-    dumpLog("__start_report" +
-            duration         +
-            "__end_report\n\n");
+    Profiler.pause("This test measures the time between sessionRestoreInit and sessionRestored, ignore everything around that");
+    Profiler.initFromURLQueryParams(location.search);
+    Profiler.finishStartupProfiling();
 
-    // Next one is required by the test harness but not used
-    dumpLog("__startTimestamp" +
-            Date.now()         +
-            "__endTimestamp\n\n");
+    setTimeout(function () {
+      // Show result on screen. Nice but not really necessary.
+      document.getElementById("sessionRestoreInit-to-sessionRestored").textContent = duration + "ms";
 
-    goQuitApplication();
-  }, 0);
-}
-
-function main() {
-  // Collect (and display) data
-  var startup_info = Services.startup.getStartupInfo();
+      // Report data to Talos, if possible
+      dumpLog("__start_report" +
+              duration         +
+              "__end_report\n\n");
 
-  // The script may be triggered before or after sessionRestored
-  // and sessionRestoreInit are available. If both are available,
-  // we are done.
-  if (typeof startup_info.sessionRestored != "undefined"
-   && typeof startup_info.sessionRestoreInit != "undefined") {
-    finish();
-    return;
-  }
+      // Next one is required by the test harness but not used
+      dumpLog("__startTimestamp" +
+              Date.now()         +
+              "__endTimestamp\n\n");
 
-  // Otherwise, we need to wait until *after* sesionstore-windows-restored,
-  // which is the event that sets sessionRestored - since sessionRestoreInit
-  // is set before sessionRestored, we are certain that both are now set.
-  Services.obs.addObserver(finish, "sessionstore-windows-restored", false);
-}
+      goQuitApplication();
+    }, 0);
+});
 
-main();
+// In case the add-on has broadcasted the message before we were loaded,
+// request a second broadcast.
+Services.cpmm.sendAsyncMessage(MSG_REQUEST, {});
new file mode 100644
--- /dev/null
+++ b/testing/talos/talos/startup_test/sessionrestore/profile/sessionCheckpoints.json
@@ -0,0 +1,1 @@
+{"profile-after-change":true,"final-ui-startup":true,"sessionstore-windows-restored":true,"quit-application-granted":true,"quit-application":true,"sessionstore-final-state-write-complete":true,"profile-change-net-teardown":true,"profile-change-teardown":true,"profile-before-change":true}
--- a/testing/talos/talos/test.py
+++ b/testing/talos/talos/test.py
@@ -143,24 +143,25 @@ class ts_paint(TsBase):
 class sessionrestore(TsBase):
     """
     A start up test measuring the time it takes to load a sessionstore.js file.
 
     1. Set up Firefox to restore from a given sessionstore.js file.
     2. Launch Firefox.
     3. Measure the delta between firstPaint and sessionRestored.
     """
+    extensions = '${talos}/startup_test/sessionrestore/addon'
     cycles = 10
     timeout = 1000000
     sps_profile_startup = True
     sps_profile_entries = 10000000
     profile_path = '${talos}/startup_test/sessionrestore/profile'
     url = 'startup_test/sessionrestore/index.html'
     shutdown = False
-    reinstall = ['sessionstore.js']
+    reinstall = ['sessionstore.js', 'sessionCheckpoints.json']
     # Restore the session
     preferences = {'browser.startup.page': 3}
 
 
 @register_test()
 class sessionrestore_no_auto_restore(sessionrestore):
     """
     A start up test measuring the time it takes to load a sessionstore.js file.
--- a/testing/taskcluster/mach_commands.py
+++ b/testing/taskcluster/mach_commands.py
@@ -6,16 +6,18 @@
 
 from __future__ import absolute_import
 
 from collections import defaultdict
 import os
 import json
 import copy
 import sys
+import time
+from collections import namedtuple
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
 
@@ -198,16 +200,54 @@ def remove_caches_from_task(task):
     try:
         caches = task["task"]["payload"]["cache"]
         for cache in caches.keys():
             if cache not in whitelist:
                 caches.pop(cache)
     except KeyError:
         pass
 
+def query_pushinfo(repository, revision):
+    """Query the pushdate and pushid of a repository/revision.
+    This is intended to be used on hg.mozilla.org/mozilla-central and
+    similar. It may or may not work for other hg repositories.
+    """
+    PushInfo = namedtuple('PushInfo', ['pushid', 'pushdate'])
+
+    try:
+        import urllib2
+        url = '%s/json-pushes?changeset=%s' % (repository, revision)
+        sys.stderr.write("Querying URL for pushdate: %s\n" % url)
+        contents = json.load(urllib2.urlopen(url))
+
+        # The contents should be something like:
+        # {
+        #   "28537": {
+        #    "changesets": [
+        #     "1d0a914ae676cc5ed203cdc05c16d8e0c22af7e5",
+        #    ],
+        #    "date": 1428072488,
+        #    "user": "user@mozilla.com"
+        #   }
+        # }
+        #
+        # So we grab the first element ("28537" in this case) and then pull
+        # out the 'date' field.
+        pushid = contents.iterkeys().next()
+        pushdate = contents[pushid]['date']
+        return PushInfo(pushid, pushdate)
+
+    except Exception:
+        sys.stderr.write(
+            "Error querying pushinfo for repository '%s' revision '%s'\n" % (
+                repository, revision,
+            )
+        )
+        return None
+
 @CommandProvider
 class DecisionTask(object):
     @Command('taskcluster-decision', category="ci",
         description="Build a decision task")
     @CommandArgument('--project',
         required=True,
         help='Treeherder project name')
     @CommandArgument('--url',
@@ -328,27 +368,37 @@ class Graph(object):
         job_path = job_path if os.path.exists(job_path) else DEFAULT_JOB_PATH
 
         jobs = templates.load(job_path, {})
 
         job_graph = parse_commit(message, jobs)
 
         cmdline_interactive = params.get('interactive', False)
 
+        # Default to current time if querying the head rev fails
+        pushdate = time.strftime('%Y%m%d%H%M%S', time.gmtime())
+        pushinfo = query_pushinfo(params['head_repository'], params['head_rev'])
+        if pushinfo:
+            pushdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(pushinfo.pushdate))
+
         # Template parameters used when expanding the graph
         parameters = dict(gaia_info().items() + {
             'index': 'index',
             'project': project,
             'pushlog_id': params.get('pushlog_id', 0),
             'docker_image': docker_image,
             'base_repository': params['base_repository'] or \
                 params['head_repository'],
             'head_repository': params['head_repository'],
             'head_ref': params['head_ref'] or params['head_rev'],
             'head_rev': params['head_rev'],
+            'pushdate': pushdate,
+            'year': pushdate[0:4],
+            'month': pushdate[4:6],
+            'day': pushdate[6:8],
             'owner': params['owner'],
             'from_now': json_time_from_now,
             'now': current_json_time(),
             'revision_hash': params['revision_hash']
         }.items())
 
         treeherder_route = '{}.{}'.format(
             params['project'],
--- a/testing/taskcluster/routes.json
+++ b/testing/taskcluster/routes.json
@@ -1,16 +1,18 @@
 {
     "routes": [
         "{index}.gecko.v2.{project}.revision.{head_rev}.{build_product}.{build_name}-{build_type}",
+        "{index}.gecko.v2.{project}.pushdate.{year}.{month}.{day}.{pushdate}.{build_product}.{build_name}-{build_type}",
         "{index}.gecko.v2.{project}.latest.{build_product}.{build_name}-{build_type}"
     ],
     "nightly": [
         "{index}.gecko.v2.{project}.nightly.{year}.{month}.{day}.revision.{head_rev}.{build_product}.{build_name}-{build_type}",
         "{index}.gecko.v2.{project}.nightly.{year}.{month}.{day}.latest.{build_product}.{build_name}-{build_type}",
         "{index}.gecko.v2.{project}.nightly.revision.{head_rev}.{build_product}.{build_name}-{build_type}",
         "{index}.gecko.v2.{project}.nightly.latest.{build_product}.{build_name}-{build_type}"
     ],
     "l10n": [
         "{index}.gecko.v2.{project}.revision.{head_rev}.{build_product}-l10n.{build_name}-{build_type}.{locale}",
+        "{index}.gecko.v2.{project}.pushdate.{year}.{month}.{day}.{pushdate}.{build_product}-l10n.{build_name}-{build_type}.{locale}",
         "{index}.gecko.v2.{project}.latest.{build_product}-l10n.{build_name}-{build_type}.{locale}"
     ]
 }
--- a/testing/tools/screenshot/gdk-screenshot.cpp
+++ b/testing/tools/screenshot/gdk-screenshot.cpp
@@ -154,8 +154,16 @@ int main(int argc, char** argv)
   if (error) {
     fprintf(stderr, "%s: failed to write screenshot as png: %s\n",
             argv[0], error->message);
     return error->code;
   }
 
   return 0;
 }
+
+// These options are copied from mozglue/build/AsanOptions.cpp
+#ifdef MOZ_ASAN
+extern "C"
+const char* __asan_default_options() {
+  return "allow_user_segv_handler=1:alloc_dealloc_mismatch=0:detect_leaks=0";
+}
+#endif
--- a/testing/web-platform/mozilla/meta/MANIFEST.json
+++ b/testing/web-platform/mozilla/meta/MANIFEST.json
@@ -452,22 +452,16 @@
           }
         ],
         "service-workers/service-worker/skip-waiting.https.html": [
           {
             "path": "service-workers/service-worker/skip-waiting.https.html",
             "url": "/_mozilla/service-workers/service-worker/skip-waiting.https.html"
           }
         ],
-        "service-workers/service-worker/stashed-ports.https.html": [
-          {
-            "path": "service-workers/service-worker/stashed-ports.https.html",
-            "url": "/_mozilla/service-workers/service-worker/stashed-ports.https.html"
-          }
-        ],
         "service-workers/service-worker/state.https.html": [
           {
             "path": "service-workers/service-worker/state.https.html",
             "url": "/_mozilla/service-workers/service-worker/state.https.html"
           }
         ],
         "service-workers/service-worker/synced-state.https.html": [
           {
deleted file mode 100644
--- a/testing/web-platform/mozilla/meta/service-workers/service-worker/invalid-blobtype.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[invalid-blobtype.https.html]
-  type: testharness
-  [Verify the response of FetchEvent using XMLHttpRequest]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/mozilla/meta/service-workers/service-worker/stashed-ports.https.html.ini
+++ /dev/null
@@ -1,11 +0,0 @@
-[stashed-ports.https.html]
-  type: testharness
-  [Name set when adding port is set correctly.]
-    expected: FAIL
-
-  [Messages posted into stashed port arrive on other side.]
-    expected: FAIL
-
-  [Messages sent to stashed port arrive as global events.]
-    expected: FAIL
-
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/invalid-blobtype-worker.js
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/invalid-blobtype-worker.js
@@ -1,11 +1,10 @@
 self.addEventListener('fetch', function(event) {
     var url = event.request.url;
     if (url.indexOf('dummy?test') == -1) {
       return;
     }
     event.respondWith(new Promise(function(resolve) {
-        var headers = new Headers;
         // null byte in blob type
         resolve(new Response(new Blob([],{type: 'a\0b'})));
       }));
   });
deleted file mode 100644
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/stashed-ports-basics.js
+++ /dev/null
@@ -1,57 +0,0 @@
-importScripts('worker-testharness.js');
-
-// Helper method that waits for a reply on a port, and resolves a promise with
-// the reply.
-function wait_for_message_to_port(test, port) {
-  return new Promise(function(resolve) {
-    var resolved = false;
-    port.onmessage = test.step_func(function(event) {
-      assert_false(resolved);
-      resolved = true;
-      resolve(event.data);
-    });
-  });
-}
-
-// Similar helper method, but waits for a reply to a stashed port with a
-// particular name.
-function wait_for_message_to_stashed_port(test, name) {
-  return new Promise(function(resolve) {
-    var event_handler = function(e) {
-      if (e.source.name === name) {
-        self.ports.removeEventListener('message', event_handler);
-        resolve(e);
-      }
-    };
-    self.ports.addEventListener('message', event_handler);
-  });
-}
-
-test(function(test) {
-    var channel = new MessageChannel();
-    var name = 'somename';
-    var stashed_port = self.ports.add(name, channel.port1);
-    assert_equals(stashed_port.name, name);
-  }, 'Name set when adding port is set correctly.');
-
-promise_test(function(test) {
-    var channel = new MessageChannel();
-    var stashed_port = self.ports.add('', channel.port1);
-    stashed_port.postMessage('pingy ping');
-    return wait_for_message_to_port(test, channel.port2)
-      .then(test.step_func(function(reply) {
-          assert_equals(reply, 'pingy ping');
-      }));
-  }, 'Messages posted into stashed port arrive on other side.');
-
-promise_test(function(test) {
-    var channel = new MessageChannel();
-    var name = 'test_events';
-    var stashed_port = self.ports.add(name, channel.port1);
-    channel.port2.postMessage('ping blah');
-    return wait_for_message_to_stashed_port(test, name)
-      .then(test.step_func(function(reply) {
-          assert_equals(reply.data, 'ping blah');
-          assert_equals(reply.source, stashed_port);
-      }));
-  }, 'Messages sent to stashed port arrive as global events.');
deleted file mode 100644
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/stashed-ports.https.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<title>
-  Tests basic stashed message ports functionality.
-</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../service-worker/resources/test-helpers.sub.js"></script>
-<script>
-service_worker_test('resources/stashed-ports-basics.js',
-                    'Verifies basic functionality of stashed ports.');
-</script>
--- a/widget/cocoa/nsDragService.h
+++ b/widget/cocoa/nsDragService.h
@@ -25,19 +25,21 @@ extern NSString* const kCorePboardType_u
 - (NSPasteboard*)pasteboard;
 @end
 
 class nsDragService : public nsBaseDragService
 {
 public:
   nsDragService();
 
+  // nsBaseDragService
+  virtual nsresult InvokeDragSessionImpl(nsISupportsArray* anArrayTransferables,
+                                         nsIScriptableRegion* aRegion,
+                                         uint32_t aActionType);
   // nsIDragService
-  NS_IMETHOD InvokeDragSession(nsIDOMNode *aDOMNode, nsISupportsArray * anArrayTransferables,
-                               nsIScriptableRegion * aRegion, uint32_t aActionType);
   NS_IMETHOD EndDragSession(bool aDoneDrag);
 
   // nsIDragSession
   NS_IMETHOD GetData(nsITransferable * aTransferable, uint32_t aItemIndex);
   NS_IMETHOD IsDataFlavorSupported(const char *aDataFlavor, bool *_retval);
   NS_IMETHOD GetNumDropItems(uint32_t * aNumItems);
 
 protected:
--- a/widget/cocoa/nsDragService.mm
+++ b/widget/cocoa/nsDragService.mm
@@ -268,35 +268,31 @@ nsDragService::ConstructDragImage(nsIDOM
   return [image autorelease];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 // We can only invoke NSView's 'dragImage:at:offset:event:pasteboard:source:slideBack:' from
 // within NSView's 'mouseDown:' or 'mouseDragged:'. Luckily 'mouseDragged' is always on the
 // stack when InvokeDragSession gets called.
-NS_IMETHODIMP
-nsDragService::InvokeDragSession(nsIDOMNode* aDOMNode, nsISupportsArray* aTransferableArray,
-                                 nsIScriptableRegion* aDragRgn, uint32_t aActionType)
+nsresult
+nsDragService::InvokeDragSessionImpl(nsISupportsArray* aTransferableArray,
+                                     nsIScriptableRegion* aDragRgn,
+                                     uint32_t aActionType)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
-  nsresult rv = nsBaseDragService::InvokeDragSession(aDOMNode,
-                                                     aTransferableArray,
-                                                     aDragRgn, aActionType);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   mDataItems = aTransferableArray;
 
   // put data on the clipboard
   if (NS_FAILED(SetUpDragClipboard(aTransferableArray)))
     return NS_ERROR_FAILURE;
 
   nsIntRect dragRect(0, 0, 20, 20);
-  NSImage* image = ConstructDragImage(aDOMNode, &dragRect, aDragRgn);
+  NSImage* image = ConstructDragImage(mSourceNode, &dragRect, aDragRgn);
   if (!image) {
     // if no image was returned, just draw a rectangle
     NSSize size;
     size.width = dragRect.width;
     size.height = dragRect.height;
     image = [[NSImage alloc] initWithSize:size];
     [image lockFocus];
     [[NSColor grayColor] set];
--- a/widget/gtk/nsDragService.cpp
+++ b/widget/gtk/nsDragService.cpp
@@ -307,21 +307,26 @@ nsDragService::InvokeDragSession(nsIDOMN
 
     // If the previous source drag has not yet completed, signal handlers need
     // to be removed from sGrabWidget and dragend needs to be dispatched to
     // the source node, but we can't call EndDragSession yet because we don't
     // know whether or not the drag succeeded.
     if (mSourceNode)
         return NS_ERROR_NOT_AVAILABLE;
 
-    nsresult rv = nsBaseDragService::InvokeDragSession(aDOMNode,
-                                                       aArrayTransferables,
-                                                       aRegion, aActionType);
-    NS_ENSURE_SUCCESS(rv, rv);
+    return nsBaseDragService::InvokeDragSession(aDOMNode, aArrayTransferables,
+                                                aRegion, aActionType);
+}
 
+// nsBaseDragService
+nsresult
+nsDragService::InvokeDragSessionImpl(nsISupportsArray* aArrayTransferables,
+                                     nsIScriptableRegion* aRegion,
+                                     uint32_t aActionType)
+{
     // make sure that we have an array of transferables to use
     if (!aArrayTransferables)
         return NS_ERROR_INVALID_ARG;
     // set our reference to the transferables.  this will also addref
     // the transferables since we're going to hang onto this beyond the
     // length of this call
     mSourceDataItems = aArrayTransferables;
     // get the list of items we offer for drags
@@ -372,30 +377,32 @@ nsDragService::InvokeDragSession(nsIDOMN
     GdkDragContext *context = gtk_drag_begin(mHiddenWidget,
                                              sourceList,
                                              action,
                                              1,
                                              &event);
 
     mSourceRegion = nullptr;
 
+    nsresult rv;
     if (context) {
         StartDragSession();
 
         // GTK uses another hidden window for receiving mouse events.
         sGrabWidget = gtk_window_group_get_current_grab(window_group);
         if (sGrabWidget) {
             g_object_ref(sGrabWidget);
             // Only motion and key events are required but connect to
             // "event-after" as this is never blocked by other handlers.
             g_signal_connect(sGrabWidget, "event-after",
                              G_CALLBACK(OnSourceGrabEventAfter), this);
         }
         // We don't have a drag end point yet.
         mEndDragPoint = nsIntPoint(-1, -1);
+        rv = NS_OK;
     }
     else {
         rv = NS_ERROR_FAILURE;
     }
 
     gtk_target_list_unref(sourceList);
 
     return rv;
--- a/widget/gtk/nsDragService.h
+++ b/widget/gtk/nsDragService.h
@@ -53,16 +53,20 @@ class nsDragService final : public nsBas
 {
 public:
     nsDragService();
 
     NS_DECL_ISUPPORTS_INHERITED
 
     NS_DECL_NSIOBSERVER
 
+    // nsBaseDragService
+    virtual nsresult InvokeDragSessionImpl(nsISupportsArray* anArrayTransferables,
+                                           nsIScriptableRegion* aRegion,
+                                           uint32_t aActionType) override;
     // nsIDragService
     NS_IMETHOD InvokeDragSession (nsIDOMNode *aDOMNode,
                                   nsISupportsArray * anArrayTransferables,
                                   nsIScriptableRegion * aRegion,
                                   uint32_t aActionType) override;
     NS_IMETHOD StartDragSession() override;
     NS_IMETHOD EndDragSession(bool aDoneDrag) override;
 
--- a/widget/nsBaseDragService.cpp
+++ b/widget/nsBaseDragService.cpp
@@ -220,17 +220,25 @@ nsBaseDragService::InvokeDragSession(nsI
   mEndDragPoint = nsIntPoint(0, 0);
 
   // When the mouse goes down, the selection code starts a mouse
   // capture. However, this gets in the way of determining drag
   // feedback for things like trees because the event coordinates
   // are in the wrong coord system, so turn off mouse capture.
   nsIPresShell::ClearMouseCapture(nullptr);
 
-  return NS_OK;
+  nsresult rv = InvokeDragSessionImpl(aTransferableArray,
+                                      aDragRgn, aActionType);
+
+  if (NS_FAILED(rv)) {
+    mSourceNode = nullptr;
+    mSourceDocument = nullptr;
+  }
+
+  return rv;
 }
 
 NS_IMETHODIMP
 nsBaseDragService::InvokeDragSessionWithImage(nsIDOMNode* aDOMNode,
                                               nsISupportsArray* aTransferableArray,
                                               nsIScriptableRegion* aRegion,
                                               uint32_t aActionType,
                                               nsIDOMNode* aImage,
@@ -248,17 +256,26 @@ nsBaseDragService::InvokeDragSessionWith
   mDragPopup = nullptr;
   mImage = aImage;
   mImageOffset = CSSIntPoint(aImageX, aImageY);
 
   aDragEvent->GetScreenX(&mScreenX);
   aDragEvent->GetScreenY(&mScreenY);
   aDragEvent->GetMozInputSource(&mInputSource);
 
-  return InvokeDragSession(aDOMNode, aTransferableArray, aRegion, aActionType);
+  nsresult rv = InvokeDragSession(aDOMNode, aTransferableArray,
+                                  aRegion, aActionType);
+
+  if (NS_FAILED(rv)) {
+    mImage = nullptr;
+    mHasImage = false;
+    mDataTransfer = nullptr;
+  }
+
+  return rv;
 }
 
 NS_IMETHODIMP
 nsBaseDragService::InvokeDragSessionWithSelection(nsISelection* aSelection,
                                                   nsISupportsArray* aTransferableArray,
                                                   uint32_t aActionType,
                                                   nsIDOMDragEvent* aDragEvent,
                                                   nsIDOMDataTransfer* aDataTransfer)
@@ -279,17 +296,26 @@ nsBaseDragService::InvokeDragSessionWith
   aDragEvent->GetMozInputSource(&mInputSource);
 
   // just get the focused node from the selection
   // XXXndeakin this should actually be the deepest node that contains both
   // endpoints of the selection
   nsCOMPtr<nsIDOMNode> node;
   aSelection->GetFocusNode(getter_AddRefs(node));
 
-  return InvokeDragSession(node, aTransferableArray, nullptr, aActionType);
+  nsresult rv = InvokeDragSession(node, aTransferableArray,
+                                  nullptr, aActionType);
+
+  if (NS_FAILED(rv)) {
+    mHasImage = false;
+    mSelection = nullptr;
+    mDataTransfer = nullptr;
+  }
+
+  return rv;
 }
 
 //-------------------------------------------------------------------------
 NS_IMETHODIMP
 nsBaseDragService::GetCurrentSession(nsIDragSession ** aSession)
 {
   if (!aSession)
     return NS_ERROR_INVALID_ARG;
--- a/widget/nsBaseDragService.h
+++ b/widget/nsBaseDragService.h
@@ -59,16 +59,25 @@ public:
   uint16_t GetInputSource() { return mInputSource; }
 
   int32_t TakeChildProcessDragAction();
 
 protected:
   virtual ~nsBaseDragService();
 
   /**
+   * Called from nsBaseDragService to initiate a platform drag from a source
+   * in this process.  This is expected to ensure that StartDragSession() and
+   * EndDragSession() get called if the platform drag is successfully invoked.
+   */
+  virtual nsresult InvokeDragSessionImpl(nsISupportsArray* aTransferableArray,
+                                         nsIScriptableRegion* aDragRgn,
+                                         uint32_t aActionType) = 0;
+
+  /**
    * Draw the drag image, if any, to a surface and return it. The drag image
    * is constructed from mImage if specified, or aDOMNode if mImage is null.
    *
    * aRegion may be used to draw only a subset of the element. This region
    * should be supplied using x and y coordinates measured in css pixels
    * that are relative to the upper-left corner of the window.
    *
    * aScreenX and aScreenY should be the screen coordinates of the mouse click
--- a/widget/nsDragServiceProxy.cpp
+++ b/widget/nsDragServiceProxy.cpp
@@ -18,31 +18,22 @@ NS_IMPL_ISUPPORTS_INHERITED0(nsDragServi
 nsDragServiceProxy::nsDragServiceProxy()
 {
 }
 
 nsDragServiceProxy::~nsDragServiceProxy()
 {
 }
 
-NS_IMETHODIMP
-nsDragServiceProxy::InvokeDragSession(nsIDOMNode* aDOMNode,
-                                      nsISupportsArray* aArrayTransferables,
-                                      nsIScriptableRegion* aRegion,
-                                      uint32_t aActionType)
+nsresult
+nsDragServiceProxy::InvokeDragSessionImpl(nsISupportsArray* aArrayTransferables,
+                                          nsIScriptableRegion* aRegion,
+                                          uint32_t aActionType)
 {
-  nsresult rv = nsBaseDragService::InvokeDragSession(aDOMNode,
-                                                     aArrayTransferables,
-                                                     aRegion,
-                                                     aActionType);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIDOMDocument> sourceDocument;
-  aDOMNode->GetOwnerDocument(getter_AddRefs(sourceDocument));
-  nsCOMPtr<nsIDocument> doc = do_QueryInterface(sourceDocument);
+  nsCOMPtr<nsIDocument> doc = do_QueryInterface(mSourceDocument);
   NS_ENSURE_STATE(doc->GetDocShell());
   mozilla::dom::TabChild* child =
     mozilla::dom::TabChild::GetFrom(doc->GetDocShell());
   NS_ENSURE_STATE(child);
   nsTArray<mozilla::dom::IPCDataTransfer> dataTransfers;
   nsContentUtils::TransferablesToIPCTransferables(aArrayTransferables,
                                                   dataTransfers,
                                                   child->Manager(),
--- a/widget/nsDragServiceProxy.h
+++ b/widget/nsDragServiceProxy.h
@@ -10,18 +10,17 @@
 
 class nsDragServiceProxy : public nsBaseDragService
 {
 public:
   nsDragServiceProxy();
 
   NS_DECL_ISUPPORTS_INHERITED
 
-  // nsIDragService
-  NS_IMETHOD InvokeDragSession(nsIDOMNode* aDOMNode,
-                               nsISupportsArray* anArrayTransferables,
-                               nsIScriptableRegion* aRegion,
-                               uint32_t aActionType) override;
+  // nsBaseDragService
+  virtual nsresult InvokeDragSessionImpl(nsISupportsArray* anArrayTransferables,
+                                         nsIScriptableRegion* aRegion,
+                                         uint32_t aActionType) override;
 private:
   virtual ~nsDragServiceProxy();
 };
 
 #endif // NSDRAGSERVICEPROXY_H
--- a/widget/windows/nsDragService.cpp
+++ b/widget/windows/nsDragService.cpp
@@ -165,38 +165,31 @@ nsDragService::CreateDragImage(nsIDOMNod
   }
 
   dataSurface->Unmap();
 
   return psdi->hbmpDragImage != nullptr;
 }
 
 //-------------------------------------------------------------------------
-NS_IMETHODIMP
-nsDragService::InvokeDragSession(nsIDOMNode *aDOMNode,
-                                 nsISupportsArray *anArrayTransferables,
-                                 nsIScriptableRegion *aRegion,
-                                 uint32_t aActionType)
+nsresult
+nsDragService::InvokeDragSessionImpl(nsISupportsArray* anArrayTransferables,
+                                     nsIScriptableRegion* aRegion,
+                                     uint32_t aActionType)
 {
-  nsresult rv = nsBaseDragService::InvokeDragSession(aDOMNode,
-                                                     anArrayTransferables,
-                                                     aRegion,
-                                                     aActionType);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   // Try and get source URI of the items that are being dragged
   nsIURI *uri = nullptr;
 
   nsCOMPtr<nsIDocument> doc(do_QueryInterface(mSourceDocument));
   if (doc) {
     uri = doc->GetDocumentURI();
   }
 
   uint32_t numItemsToDrag = 0;
-  rv = anArrayTransferables->Count(&numItemsToDrag);
+  nsresult rv = anArrayTransferables->Count(&numItemsToDrag);
   if (!numItemsToDrag)
     return NS_ERROR_FAILURE;
 
   // The clipboard class contains some static utility methods that we
   // can use to create an IDataObject from the transferable
 
   // if we're dragging more than one item, we need to create a
   // "collection" object to fake out the OS. This collection contains
@@ -209,17 +202,17 @@ nsDragService::InvokeDragSession(nsIDOMN
       return NS_ERROR_OUT_OF_MEMORY;
     itemToDrag = dataObjCollection;
     for (uint32_t i=0; i<numItemsToDrag; ++i) {
       nsCOMPtr<nsISupports> supports;
       anArrayTransferables->GetElementAt(i, getter_AddRefs(supports));
       nsCOMPtr<nsITransferable> trans(do_QueryInterface(supports));
       if (trans) {
         // set the requestingNode on the transferable
-        trans->SetRequestingNode(aDOMNode);
+        trans->SetRequestingNode(mSourceNode);
         RefPtr<IDataObject> dataObj;
         rv = nsClipboard::CreateNativeDataObject(trans,
                                                  getter_AddRefs(dataObj), uri);
         NS_ENSURE_SUCCESS(rv, rv);
         // Add the flavors to the collection object too
         rv = nsClipboard::SetupNativeDataObject(trans, dataObjCollection);
         NS_ENSURE_SUCCESS(rv, rv);
 
@@ -228,31 +221,31 @@ nsDragService::InvokeDragSession(nsIDOMN
     }
   } // if dragging multiple items
   else {
     nsCOMPtr<nsISupports> supports;
     anArrayTransferables->GetElementAt(0, getter_AddRefs(supports));
     nsCOMPtr<nsITransferable> trans(do_QueryInterface(supports));
     if (trans) {
       // set the requestingNode on the transferable
-      trans->SetRequestingNode(aDOMNode);
+      trans->SetRequestingNode(mSourceNode);
       rv = nsClipboard::CreateNativeDataObject(trans,
                                                getter_AddRefs(itemToDrag),
                                                uri);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   } // else dragging a single object
 
   // Create a drag image if support is available
   IDragSourceHelper *pdsh;
   if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper, nullptr,
                                  CLSCTX_INPROC_SERVER,
                                  IID_IDragSourceHelper, (void**)&pdsh))) {
     SHDRAGIMAGE sdi;
-    if (CreateDragImage(aDOMNode, aRegion, &sdi)) {
+    if (CreateDragImage(mSourceNode, aRegion, &sdi)) {
       if (FAILED(pdsh->InitializeFromBitmap(&sdi, itemToDrag)))
         DeleteObject(sdi.hbmpDragImage);
     }
     pdsh->Release();
   }
 
   // Kick off the native drag session
   return StartInvokingDragSession(itemToDrag, aActionType);
--- a/widget/windows/nsDragService.h
+++ b/widget/windows/nsDragService.h
@@ -18,21 +18,20 @@ class  nsDataObjCollection;
  */
 
 class nsDragService : public nsBaseDragService
 {
 public:
   nsDragService();
   virtual ~nsDragService();
   
-  // nsIDragService
-  NS_IMETHOD InvokeDragSession(nsIDOMNode *aDOMNode,
-                               nsISupportsArray *anArrayTransferables,
-                               nsIScriptableRegion *aRegion,
-                               uint32_t aActionType);
+  // nsBaseDragService
+  virtual nsresult InvokeDragSessionImpl(nsISupportsArray* anArrayTransferables,
+                                         nsIScriptableRegion* aRegion,
+                                         uint32_t aActionType);
 
   // nsIDragSession
   NS_IMETHOD GetData(nsITransferable * aTransferable, uint32_t anItem);
   NS_IMETHOD GetNumDropItems(uint32_t * aNumItems);
   NS_IMETHOD IsDataFlavorSupported(const char *aDataFlavor, bool *_retval);
   NS_IMETHOD EndDragSession(bool aDoneDrag);
 
   // native impl.
--- a/xpcom/glue/nsTHashtable.h
+++ b/xpcom/glue/nsTHashtable.h
@@ -2,19 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsTHashtable_h__
 #define nsTHashtable_h__
 
-#include "nscore.h"
 #include "PLDHashTable.h"
-#include "nsDebug.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/fallible.h"
 #include "mozilla/MemoryChecking.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Move.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/TypeTraits.h"
--- a/xpfe/appshell/nsXULWindow.cpp
+++ b/xpfe/appshell/nsXULWindow.cpp
@@ -1473,16 +1473,21 @@ NS_IMETHODIMP nsXULWindow::SavePersisten
 
   nsAutoString   persistString;
   docShellElement->GetAttribute(PERSIST_ATTRIBUTE, persistString);
   if (persistString.IsEmpty()) { // quick check which sometimes helps
     mPersistentAttributesDirty = 0;
     return NS_OK;
   }
 
+  bool isFullscreen = false;
+  if (nsPIDOMWindow* domWindow = mDocShell->GetWindow()) {
+    domWindow->GetFullScreen(&isFullscreen);
+  }
+
   // get our size, position and mode to persist
   nsIntRect rect;
   bool gotRestoredBounds = NS_SUCCEEDED(mWindow->GetRestoredBounds(rect));
 
   CSSToLayoutDeviceScale scale = mWindow->GetDefaultScale();
 
   // make our position relative to our parent, if any
   nsCOMPtr<nsIBaseWindow> parent(do_QueryReferent(mParentWindow));
@@ -1500,75 +1505,83 @@ NS_IMETHODIMP nsXULWindow::SavePersisten
   nsCOMPtr<nsIDOMXULDocument> ownerXULDoc;
 
   // fetch docShellElement's ID and XUL owner document
   ownerXULDoc = do_QueryInterface(docShellElement->OwnerDoc());
   if (docShellElement->IsXULElement()) {
     docShellElement->GetId(windowElementId);
   }
 
+  bool shouldPersist = !isFullscreen && ownerXULDoc;
   ErrorResult rv;
   // (only for size elements which are persisted)
   if ((mPersistentAttributesDirty & PAD_POSITION) && gotRestoredBounds) {
     if (persistString.Find("screenX") >= 0) {
       PR_snprintf(sizeBuf, sizeof(sizeBuf), "%d", NSToIntRound(rect.x / scale.scale));
       sizeString.AssignWithConversion(sizeBuf);
       docShellElement->SetAttribute(SCREENX_ATTRIBUTE, sizeString, rv);
-      if (ownerXULDoc) // force persistence in case the value didn't change
+      if (shouldPersist) {
         ownerXULDoc->Persist(windowElementId, SCREENX_ATTRIBUTE);
+      }
     }
     if (persistString.Find("screenY") >= 0) {
       PR_snprintf(sizeBuf, sizeof(sizeBuf), "%d", NSToIntRound(rect.y / scale.scale));
       sizeString.AssignWithConversion(sizeBuf);
       docShellElement->SetAttribute(SCREENY_ATTRIBUTE, sizeString, rv);
-      if (ownerXULDoc)
+      if (shouldPersist) {
         ownerXULDoc->Persist(windowElementId, SCREENY_ATTRIBUTE);
+      }
     }
   }
 
   if ((mPersistentAttributesDirty & PAD_SIZE) && gotRestoredBounds) {
     if (persistString.Find("width") >= 0) {
       PR_snprintf(sizeBuf, sizeof(sizeBuf), "%d", NSToIntRound(rect.width / scale.scale));
       sizeString.AssignWithConversion(sizeBuf);
       docShellElement->SetAttribute(WIDTH_ATTRIBUTE, sizeString, rv);
-      if (ownerXULDoc)
+      if (shouldPersist) {
         ownerXULDoc->Persist(windowElementId, WIDTH_ATTRIBUTE);
+      }
     }
     if (persistString.Find("height") >= 0) {
       PR_snprintf(sizeBuf, sizeof(sizeBuf), "%d", NSToIntRound(rect.height / scale.scale));
       sizeString.AssignWithConversion(sizeBuf);
       docShellElement->SetAttribute(HEIGHT_ATTRIBUTE, sizeString, rv);
-      if (ownerXULDoc)
+      if (shouldPersist) {
         ownerXULDoc->Persist(windowElementId, HEIGHT_ATTRIBUTE);
+      }
     }
   }
 
   if (mPersistentAttributesDirty & PAD_MISC) {
     nsSizeMode sizeMode = mWindow->SizeMode();
 
     if (sizeMode != nsSizeMode_Minimized) {
       if (sizeMode == nsSizeMode_Maximized)
         sizeString.Assign(SIZEMODE_MAXIMIZED);
       else if (sizeMode == nsSizeMode_Fullscreen)
         sizeString.Assign(SIZEMODE_FULLSCREEN);
       else
         sizeString.Assign(SIZEMODE_NORMAL);
       docShellElement->SetAttribute(MODE_ATTRIBUTE, sizeString, rv);
-      if (ownerXULDoc && persistString.Find("sizemode") >= 0)
+      if (shouldPersist && persistString.Find("sizemode") >= 0) {
         ownerXULDoc->Persist(windowElementId, MODE_ATTRIBUTE);
+      }
     }
     if (persistString.Find("zlevel") >= 0) {
       uint32_t zLevel;
       nsCOMPtr<nsIWindowMediator> mediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
       if (mediator) {
         mediator->GetZLevel(this, &zLevel);
         PR_snprintf(sizeBuf, sizeof(sizeBuf), "%lu", (unsigned long)zLevel);
         sizeString.AssignWithConversion(sizeBuf);
         docShellElement->SetAttribute(ZLEVEL_ATTRIBUTE, sizeString, rv);
-        ownerXULDoc->Persist(windowElementId, ZLEVEL_ATTRIBUTE);
+        if (shouldPersist) {
+          ownerXULDoc->Persist(windowElementId, ZLEVEL_ATTRIBUTE);
+        }
       }
     }
   }
 
   mPersistentAttributesDirty = 0;
   return NS_OK;
 }