Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 19 Aug 2015 17:03:29 -0700
changeset 291063 29b2df16e961fbe9a379362ecba6f888d1754bc3
parent 291011 0d5a08e1619be257cfe9fbda2024011cfca691f8 (current diff)
parent 291062 9caee7468e3950e5b4c9e50bd07e9b20ef51fabf (diff)
child 291067 4cb7b8d73d2dce1dbae696e5518efad5a6154df5
child 291107 e0acb15cefd63db9bf1a5f467d3a8434ad4b54cd
child 291161 1d520276409de8ccf8fc8bd26c1376e1effaeef6
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone43.0a1
first release with
nightly linux32
29b2df16e961 / 43.0a1 / 20150820030202 / files
nightly linux64
29b2df16e961 / 43.0a1 / 20150820030202 / files
nightly mac
29b2df16e961 / 43.0a1 / 20150820030202 / files
nightly win32
29b2df16e961 / 43.0a1 / 20150820030202 / files
nightly win64
29b2df16e961 / 43.0a1 / 20150820030202 / 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 inbound to central, a=merge
dom/media/AudioSink.cpp
dom/media/AudioSink.h
toolkit/components/perfmonitoring/tests/xpcshell/test_compartments.js
toolkit/components/perfmonitoring/tests/xpcshell/xpcshell.ini
--- a/accessible/windows/ProxyWrappers.h
+++ b/accessible/windows/ProxyWrappers.h
@@ -24,22 +24,26 @@ class ProxyAccessibleWrap : public Acces
   }
 
   virtual void Shutdown() override
   {
     mBits.proxy = nullptr;
   }
 };
 
-class HyperTextProxyAccessibleWrap : public ProxyAccessibleWrap,
-                                     public ia2AccessibleEditableText,
-                                     public ia2AccessibleHypertext
+class HyperTextProxyAccessibleWrap : public HyperTextAccessibleWrap
 {
   HyperTextProxyAccessibleWrap(ProxyAccessible* aProxy) :
-    ProxyAccessibleWrap(aProxy) {}
+    HyperTextAccessibleWrap(nullptr, nullptr)
+  {
+    mType = eProxyType;
+    mBits.proxy = aProxy;
+  }
+
+  virtual void Shutdown() override { mBits.proxy = nullptr; }
 };
 
 template<typename T>
 inline ProxyAccessible*
 HyperTextProxyFor(T* aWrapper)
 {
   static_assert(mozilla::IsBaseOf<IUnknown, T>::value, "only IAccessible* should be passed in");
   auto wrapper = static_cast<HyperTextProxyAccessibleWrap*>(aWrapper);
--- a/accessible/windows/ia2/ia2AccessibleText.cpp
+++ b/accessible/windows/ia2/ia2AccessibleText.cpp
@@ -8,19 +8,26 @@
 #include "ia2AccessibleText.h"
 
 #include "Accessible2.h"
 #include "AccessibleText_i.c"
 
 #include "HyperTextAccessibleWrap.h"
 #include "HyperTextAccessible-inl.h"
 #include "ProxyWrappers.h"
+#include "mozilla/ClearOnShutdown.h"
 
 using namespace mozilla::a11y;
 
+StaticRefPtr<HyperTextAccessibleWrap> ia2AccessibleText::sLastTextChangeAcc;
+StaticAutoPtr<nsString> ia2AccessibleText::sLastTextChangeString;
+uint32_t ia2AccessibleText::sLastTextChangeStart = 0;
+uint32_t ia2AccessibleText::sLastTextChangeEnd = 0;
+bool ia2AccessibleText::sLastTextChangeWasInsert = false;
+
 // IAccessibleText
 
 STDMETHODIMP
 ia2AccessibleText::addSelection(long aStartOffset, long aEndOffset)
 {
   A11Y_TRYBLOCK_BEGIN
 
   if (ProxyAccessible* proxy = HyperTextProxyFor(this)) {
@@ -567,31 +574,32 @@ ia2AccessibleText::get_oldText(IA2TextSe
 
 HRESULT
 ia2AccessibleText::GetModifiedText(bool aGetInsertedText,
                                    IA2TextSegment *aText)
 {
   if (!aText)
     return E_INVALIDARG;
 
-  uint32_t startOffset = 0, endOffset = 0;
-  nsAutoString text;
+  if (!sLastTextChangeAcc)
+    return S_OK;
+
+  if (aGetInsertedText != sLastTextChangeWasInsert)
+    return S_OK;
 
-  nsresult rv = GetModifiedText(aGetInsertedText, text,
-                                &startOffset, &endOffset);
-  if (NS_FAILED(rv))
-    return GetHRESULT(rv);
+  if (sLastTextChangeAcc != this)
+    return S_OK;
 
-  aText->start = startOffset;
-  aText->end = endOffset;
+  aText->start = sLastTextChangeStart;
+  aText->end = sLastTextChangeEnd;
 
-  if (text.IsEmpty())
+  if (sLastTextChangeString->IsEmpty())
     return S_FALSE;
 
-  aText->text = ::SysAllocStringLen(text.get(), text.Length());
+  aText->text = ::SysAllocStringLen(sLastTextChangeString->get(), sLastTextChangeString->Length());
   return aText->text ? S_OK : E_OUTOFMEMORY;
 }
 
 AccessibleTextBoundary
 ia2AccessibleText::GetGeckoTextBoundary(enum IA2TextBoundaryType aBoundaryType)
 {
   switch (aBoundaryType) {
     case IA2_TEXT_BOUNDARY_CHAR:
@@ -603,8 +611,29 @@ ia2AccessibleText::GetGeckoTextBoundary(
     //case IA2_TEXT_BOUNDARY_SENTENCE:
     //case IA2_TEXT_BOUNDARY_PARAGRAPH:
       // XXX: not implemented
     default:
       return -1;
   }
 }
 
+void
+ia2AccessibleText::InitTextChangeData()
+{
+  ClearOnShutdown(&sLastTextChangeAcc);
+  ClearOnShutdown(&sLastTextChangeString);
+}
+
+void
+ia2AccessibleText::UpdateTextChangeData(HyperTextAccessibleWrap* aAcc,
+                                        bool aInsert, const nsString& aStr,
+                                        int32_t aStart, uint32_t aLen)
+{
+  if (!sLastTextChangeString)
+    sLastTextChangeString = new nsString();
+
+  sLastTextChangeAcc = aAcc;
+  sLastTextChangeStart = aStart;
+  sLastTextChangeEnd = aStart + aLen;
+  sLastTextChangeWasInsert = aInsert;
+  *sLastTextChangeString = aStr;
+}
--- a/accessible/windows/ia2/ia2AccessibleText.h
+++ b/accessible/windows/ia2/ia2AccessibleText.h
@@ -9,16 +9,17 @@
 #define _ACCESSIBLE_TEXT_H
 
 #include "nsIAccessibleText.h"
 
 #include "AccessibleText.h"
 
 namespace mozilla {
 namespace a11y {
+class HyperTextAccessibleWrap;
 
 class ia2AccessibleText: public IAccessibleText
 {
 public:
 
   // IAccessibleText
   virtual HRESULT STDMETHODCALLTYPE addSelection(
       /* [in] */ long startOffset,
@@ -108,20 +109,27 @@ public:
       /* [in] */ long y);
 
   virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_newText(
       /* [retval][out] */ IA2TextSegment *newText);
 
   virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_oldText(
       /* [retval][out] */ IA2TextSegment *oldText);
 
+  static void InitTextChangeData();
+  static void UpdateTextChangeData(HyperTextAccessibleWrap* aAcc, bool aInsert,
+                                   const nsString& aStr, int32_t aStart,
+                                   uint32_t aLen);
+
 protected:
-  virtual nsresult GetModifiedText(bool aGetInsertedText, nsAString& aText,
-                                   uint32_t *aStartOffset,
-                                   uint32_t *aEndOffset) = 0;
+  static StaticRefPtr<HyperTextAccessibleWrap> sLastTextChangeAcc;
+  static StaticAutoPtr<nsString> sLastTextChangeString;
+  static bool sLastTextChangeWasInsert;
+  static uint32_t sLastTextChangeStart;
+  static uint32_t sLastTextChangeEnd;
 
 private:
   HRESULT GetModifiedText(bool aGetInsertedText, IA2TextSegment *aNewText);
   AccessibleTextBoundary GetGeckoTextBoundary(enum IA2TextBoundaryType coordinateType);
 };
 
 } // namespace a11y
 } // namespace mozilla
--- a/accessible/windows/msaa/HyperTextAccessibleWrap.cpp
+++ b/accessible/windows/msaa/HyperTextAccessibleWrap.cpp
@@ -10,22 +10,16 @@
 
 #include "nsEventShell.h"
 
 #include "mozilla/StaticPtr.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
-StaticRefPtr<Accessible> HyperTextAccessibleWrap::sLastTextChangeAcc;
-StaticAutoPtr<nsString> HyperTextAccessibleWrap::sLastTextChangeString;
-uint32_t HyperTextAccessibleWrap::sLastTextChangeStart = 0;
-uint32_t HyperTextAccessibleWrap::sLastTextChangeEnd = 0;
-bool HyperTextAccessibleWrap::sLastTextChangeWasInsert = false;
-
 NS_IMPL_ISUPPORTS_INHERITED0(HyperTextAccessibleWrap,
                              HyperTextAccessible)
 
 STDMETHODIMP
 HyperTextAccessibleWrap::QueryInterface(REFIID aIID, void** aInstancePtr)
 {
   if (!aInstancePtr)
     return E_FAIL;
@@ -54,49 +48,20 @@ nsresult
 HyperTextAccessibleWrap::HandleAccEvent(AccEvent* aEvent)
 {
   uint32_t eventType = aEvent->GetEventType();
 
   if (eventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED ||
       eventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED) {
     Accessible* accessible = aEvent->GetAccessible();
     if (accessible && accessible->IsHyperText()) {
-      sLastTextChangeAcc = accessible;
-      if (!sLastTextChangeString)
-        sLastTextChangeString = new nsString();
-
       AccTextChangeEvent* event = downcast_accEvent(aEvent);
-      event->GetModifiedText(*sLastTextChangeString);
-      sLastTextChangeStart = event->GetStartOffset();
-      sLastTextChangeEnd = sLastTextChangeStart + event->GetLength();
-      sLastTextChangeWasInsert = event->IsTextInserted();
+        HyperTextAccessibleWrap* text =
+          static_cast<HyperTextAccessibleWrap*>(accessible->AsHyperText());
+      ia2AccessibleText::UpdateTextChangeData(text, event->IsTextInserted(),
+                                              event->ModifiedText(),
+                                              event->GetStartOffset(),
+                                              event->GetLength());
     }
   }
 
   return HyperTextAccessible::HandleAccEvent(aEvent);
 }
-
-nsresult
-HyperTextAccessibleWrap::GetModifiedText(bool aGetInsertedText,
-                                         nsAString& aText,
-                                         uint32_t* aStartOffset,
-                                         uint32_t* aEndOffset)
-{
-  aText.Truncate();
-  *aStartOffset = 0;
-  *aEndOffset = 0;
-
-  if (!sLastTextChangeAcc)
-    return NS_OK;
-
-  if (aGetInsertedText != sLastTextChangeWasInsert)
-    return NS_OK;
-
-  if (sLastTextChangeAcc != this)
-    return NS_OK;
-
-  *aStartOffset = sLastTextChangeStart;
-  *aEndOffset = sLastTextChangeEnd;
-  aText.Append(*sLastTextChangeString);
-
-  return NS_OK;
-}
-
--- a/accessible/windows/msaa/HyperTextAccessibleWrap.h
+++ b/accessible/windows/msaa/HyperTextAccessibleWrap.h
@@ -33,26 +33,14 @@ public:
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
 
   // Accessible
   virtual nsresult HandleAccEvent(AccEvent* aEvent);
 
 protected:
   ~HyperTextAccessibleWrap() {}
-
-  virtual nsresult GetModifiedText(bool aGetInsertedText, nsAString& aText,
-                                   uint32_t *aStartOffset,
-                                   uint32_t *aEndOffset);
-
-  static StaticRefPtr<Accessible> sLastTextChangeAcc;
-  static StaticAutoPtr<nsString> sLastTextChangeString;
-  static bool sLastTextChangeWasInsert;
-  static uint32_t sLastTextChangeStart;
-  static uint32_t sLastTextChangeEnd;
-
-  friend void PlatformInit();
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/windows/msaa/Platform.cpp
+++ b/accessible/windows/msaa/Platform.cpp
@@ -4,33 +4,31 @@
  * 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 "Platform.h"
 
 #include "AccEvent.h"
 #include "Compatibility.h"
 #include "HyperTextAccessibleWrap.h"
+#include "ia2AccessibleText.h"
 #include "nsWinUtils.h"
 #include "mozilla/a11y/ProxyAccessible.h"
 #include "ProxyWrappers.h"
 
-#include "mozilla/ClearOnShutdown.h"
-
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 void
 a11y::PlatformInit()
 {
   Compatibility::Init();
 
   nsWinUtils::MaybeStartWindowEmulation();
-  ClearOnShutdown(&HyperTextAccessibleWrap::sLastTextChangeAcc);
-  ClearOnShutdown(&HyperTextAccessibleWrap::sLastTextChangeString);
+  ia2AccessibleText::InitTextChangeData();
 }
 
 void
 a11y::PlatformShutdown()
 {
   ::DestroyCaret();
 
   nsWinUtils::ShutdownWindowEmulation();
@@ -69,12 +67,17 @@ a11y::ProxyStateChangeEvent(ProxyAccessi
 }
 
 void
 a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
 {
 }
 
 void
-a11y::ProxyTextChangeEvent(ProxyAccessible*, const nsString&, int32_t, uint32_t,
-                     bool, bool)
+a11y::ProxyTextChangeEvent(ProxyAccessible* aText, const nsString& aStr,
+                           int32_t aStart, uint32_t aLen, bool aInsert, bool)
 {
+  AccessibleWrap* wrapper = WrapperFor(aText);
+  auto text = static_cast<HyperTextAccessibleWrap*>(wrapper->AsHyperText());
+  if (text) {
+    ia2AccessibleText::UpdateTextChangeData(text, aInsert, aStr, aStart, aLen);
+  }
 }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2562,16 +2562,19 @@
 
             // Unmap old outerWindowIDs.
             this._outerWindowIDBrowserMap.delete(ourBrowser.outerWindowID);
             let remoteBrowser = aOtherBrowser.ownerDocument.defaultView.gBrowser;
             if (remoteBrowser) {
               remoteBrowser._outerWindowIDBrowserMap.delete(aOtherBrowser.outerWindowID);
             }
 
+            aOtherBrowser.docShellIsActive = (ourBrowser == this.selectedBrowser &&
+                                              window.windowState != window.STATE_MINIMIZED);
+
             // Swap the docshells
             ourBrowser.swapDocShells(aOtherBrowser);
 
             if (ourBrowser.isRemoteBrowser) {
               // Switch outerWindowIDs for remote browsers.
               let ourOuterWindowID = ourBrowser._outerWindowID;
               ourBrowser._outerWindowID = aOtherBrowser._outerWindowID;
               aOtherBrowser._outerWindowID = ourOuterWindowID;
--- a/browser/experiments/test/xpcshell/xpcshell.ini
+++ b/browser/experiments/test/xpcshell/xpcshell.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 head = head.js
 tail =
+tags = addons
 firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 support-files =
   experiments_1.manifest
   experiment-1.xpi
   experiment-1a.xpi
   experiment-2.xpi
   experiment-racybranch.xpi
--- a/chrome/test/unit/xpcshell.ini
+++ b/chrome/test/unit/xpcshell.ini
@@ -8,13 +8,14 @@ support-files = data/**
 [test_bug292789.js]
 [test_bug380398.js]
 [test_bug397073.js]
 [test_bug399707.js]
 [test_bug401153.js]
 [test_bug415367.js]
 [test_bug519468.js]
 [test_bug564667.js]
+tags = addons
 [test_bug848297.js]
 [test_crlf.js]
 [test_data_protocol_registration.js]
 [test_no_remote_registration.js]
 [test_resolve_uris.js]
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -46,19 +46,19 @@ Animation::WrapObject(JSContext* aCx, JS
 //
 // Animation interface:
 //
 // ---------------------------------------------------------------------------
 
 void
 Animation::SetEffect(KeyframeEffectReadOnly* aEffect)
 {
-  // FIXME: We should perform an early return if aEffect == mEffect but
-  // current nsAnimationManager::CheckAnimationRule is relying on this
-  // method updating timing even in that case.
+  if (mEffect == aEffect) {
+    return;
+  }
   if (mEffect) {
     mEffect->SetParentTime(Nullable<TimeDuration>());
   }
   mEffect = aEffect;
   if (mEffect) {
     mEffect->SetParentTime(GetCurrentTime());
   }
 
@@ -645,16 +645,26 @@ Animation::ComposeStyle(nsRefPtr<AnimVal
     if (updatedHoldTime) {
       UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
     }
 
     mFinishedAtLastComposeStyle = (playState == AnimationPlayState::Finished);
   }
 }
 
+void
+Animation::NotifyEffectTimingUpdated()
+{
+  MOZ_ASSERT(mEffect,
+             "We should only update timing effect when we have a target "
+             "effect");
+  UpdateTiming(Animation::SeekFlag::NoSeek,
+               Animation::SyncNotifyFlag::Async);
+}
+
 // http://w3c.github.io/web-animations/#play-an-animation
 void
 Animation::DoPlay(ErrorResult& aRv, LimitBehavior aLimitBehavior)
 {
   bool abortedPause = mPendingState == PendingState::PausePending;
 
   Nullable<TimeDuration> currentTime = GetCurrentTime();
   if (mPlaybackRate > 0.0 &&
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -292,16 +292,19 @@ public:
    * properties that are changed are added to |aSetProperties|.
    * |aNeedsRefreshes| will be set to true if this animation expects to update
    * the style rule on the next refresh driver tick as well (because it
    * is running and has an effect to sample).
    */
   void ComposeStyle(nsRefPtr<AnimValuesStyleRule>& aStyleRule,
                     nsCSSPropertySet& aSetProperties,
                     bool& aNeedsRefreshes);
+
+  void NotifyEffectTimingUpdated();
+
 protected:
   void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
   void SilentlySetPlaybackRate(double aPlaybackRate);
   void DoCancel();
   void DoPlay(ErrorResult& aRv, LimitBehavior aLimitBehavior);
   void DoPause(ErrorResult& aRv);
   void ResumeAt(const TimeDuration& aReadyTime);
   void PauseAt(const TimeDuration& aReadyTime);
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -82,16 +82,27 @@ KeyframeEffectReadOnly::WrapObject(JSCon
 }
 
 void
 KeyframeEffectReadOnly::SetParentTime(Nullable<TimeDuration> aParentTime)
 {
   mParentTime = aParentTime;
 }
 
+void
+KeyframeEffectReadOnly::SetTiming(const AnimationTiming& aTiming,
+                                  Animation& aOwningAnimation)
+{
+  if (mTiming == aTiming) {
+    return;
+  }
+  mTiming = aTiming;
+  aOwningAnimation.NotifyEffectTimingUpdated();
+}
+
 ComputedTiming
 KeyframeEffectReadOnly::GetComputedTimingAt(
                           const Nullable<TimeDuration>& aLocalTime,
                           const AnimationTiming& aTiming)
 {
   const TimeDuration zeroDuration;
 
   // Currently we expect negative durations to be picked up during CSS
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -237,16 +237,20 @@ public:
 
   const AnimationTiming& Timing() const {
     return mTiming;
   }
   AnimationTiming& Timing() {
     return mTiming;
   }
 
+  // FIXME: Drop |aOwningAnimation| once we make AnimationEffects track their
+  // owning animation.
+  void SetTiming(const AnimationTiming& aTiming, Animation& aOwningAnimtion);
+
   // Return the duration from the start the active interval to the point where
   // the animation begins playback. This is zero unless the animation has
   // a negative delay in which case it is the absolute value of the delay.
   // This is used for setting the elapsedTime member of CSS AnimationEvents.
   TimeDuration InitialAdvance() const {
     return std::max(TimeDuration(), mTiming.mDelay * -1);
   }
 
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -1895,17 +1895,17 @@ GK_ATOM(ondeviceproximity, "ondeviceprox
 GK_ATOM(onmozorientationchange, "onmozorientationchange")
 GK_ATOM(onuserproximity, "onuserproximity")
 
 // light sensor support
 GK_ATOM(ondevicelight, "ondevicelight")
 
 // Audio channel events
 GK_ATOM(onmozinterruptbegin, "onmozinterruptbegin")
-GK_ATOM(onmozinterruptend, "onmozinterruptbegin")
+GK_ATOM(onmozinterruptend, "onmozinterruptend")
 
 //---------------------------------------------------------------------------
 // Special atoms
 //---------------------------------------------------------------------------
 
 // Node types
 GK_ATOM(cdataTagName, "#cdata-section")
 GK_ATOM(commentTagName, "#comment")
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -433,16 +433,18 @@ TabParent::IsVisible()
 
 void
 TabParent::Destroy()
 {
   if (mIsDestroyed) {
     return;
   }
 
+  IMEStateManager::OnTabParentDestroying(this);
+
   RemoveWindowListeners();
 
   // If this fails, it's most likely due to a content-process crash,
   // and auto-cleanup will kick in.  Otherwise, the child side will
   // destroy itself and send back __delete__().
   unused << SendDestroy();
 
   if (RenderFrameParent* frame = GetRenderFrame()) {
@@ -479,16 +481,18 @@ TabParent::Recv__delete__()
   }
 
   return true;
 }
 
 void
 TabParent::ActorDestroy(ActorDestroyReason why)
 {
+  // Even though TabParent::Destroy calls this, we need to do it here too in
+  // case of a crash.
   IMEStateManager::OnTabParentDestroying(this);
 
   nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader(true);
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (frameLoader) {
     nsCOMPtr<Element> frameElement(mFrameElement);
     ReceiveMessage(CHILD_PROCESS_SHUTDOWN_MESSAGE, false, nullptr, nullptr,
                    nullptr);
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -9,17 +9,17 @@
 #include "mmsystem.h"
 #endif
 
 #include "mozilla/DebugOnly.h"
 #include <stdint.h>
 
 #include "MediaDecoderStateMachine.h"
 #include "MediaTimer.h"
-#include "AudioSink.h"
+#include "mediasink/DecodedAudioDataSink.h"
 #include "nsTArray.h"
 #include "MediaDecoder.h"
 #include "MediaDecoderReader.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/mozalloc.h"
 #include "VideoUtils.h"
 #include "TimeUnits.h"
 #include "nsDeque.h"
@@ -1067,17 +1067,17 @@ void MediaDecoderStateMachine::MaybeStar
   }
 
   DECODER_LOG("MaybeStartPlayback() starting playback");
 
   mDecoder->DispatchPlaybackStarted();
   SetPlayStartTime(TimeStamp::Now());
   MOZ_ASSERT(IsPlaying());
 
-  StartAudioThread();
+  StartAudioSink();
   StartDecodedStream();
 
   DispatchDecodeTasksIfNeeded();
 }
 
 void MediaDecoderStateMachine::UpdatePlaybackPositionInternal(int64_t aTime)
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -1456,17 +1456,17 @@ MediaDecoderStateMachine::Seek(SeekTarge
 
   DECODER_LOG("Changed state to SEEKING (to %lld)", mPendingSeek.mTarget.mTime);
   SetState(DECODER_STATE_SEEKING);
   ScheduleStateMachine();
 
   return mPendingSeek.mPromise.Ensure(__func__);
 }
 
-void MediaDecoderStateMachine::StopAudioThread()
+void MediaDecoderStateMachine::StopAudioSink()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
 
   if (mAudioSink) {
     DECODER_LOG("Shutdown audio thread");
     mAudioSink->Shutdown();
     mAudioSink = nullptr;
@@ -1749,30 +1749,30 @@ MediaDecoderStateMachine::RequestVideoDa
       ->CompletionPromise()
       ->Then(OwnerThread(), __func__, this,
              &MediaDecoderStateMachine::OnVideoDecoded,
              &MediaDecoderStateMachine::OnVideoNotDecoded));
   }
 }
 
 void
-MediaDecoderStateMachine::StartAudioThread()
+MediaDecoderStateMachine::StartAudioSink()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   if (mAudioCaptured) {
     MOZ_ASSERT(!mAudioSink);
     return;
   }
 
   if (HasAudio() && !mAudioSink) {
     mAudioCompleted = false;
-    mAudioSink = new AudioSink(mAudioQueue,
-                               GetMediaTime(), mInfo.mAudio,
-                               mDecoder->GetAudioChannel());
+    mAudioSink = new DecodedAudioDataSink(mAudioQueue,
+                                          GetMediaTime(), mInfo.mAudio,
+                                          mDecoder->GetAudioChannel());
 
     mAudioSinkPromise.Begin(
       mAudioSink->Init()->Then(
         OwnerThread(), __func__, this,
         &MediaDecoderStateMachine::OnAudioSinkComplete,
         &MediaDecoderStateMachine::OnAudioSinkError));
 
     mAudioSink->SetVolume(mVolume);
@@ -2364,22 +2364,22 @@ nsresult MediaDecoderStateMachine::RunSt
 
     case DECODER_STATE_COMPLETED: {
       if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING && IsPlaying()) {
         StopPlayback();
       }
       // Play the remaining media. We want to run AdvanceFrame() at least
       // once to ensure the current playback position is advanced to the
       // end of the media, and so that we update the readyState.
+        MaybeStartPlayback();
       if (VideoQueue().GetSize() > 1 ||
           (HasAudio() && !mAudioCompleted) ||
           (mAudioCaptured && !mDecodedStream->IsFinished()))
       {
         // Start playback if necessary to play the remaining media.
-        MaybeStartPlayback();
         UpdateRenderedVideoFrames();
         NS_ASSERTION(!IsPlaying() ||
                      mLogicallySeeking ||
                      IsStateMachineScheduled(),
                      "Must have timer scheduled");
         return NS_OK;
       }
 
@@ -2404,17 +2404,17 @@ nsresult MediaDecoderStateMachine::RunSt
         nsCOMPtr<nsIRunnable> event =
           NS_NewRunnableMethod(mDecoder, &MediaDecoder::PlaybackEnded);
         AbstractThread::MainThread()->Dispatch(event.forget());
 
         mSentPlaybackEndedEvent = true;
 
         // Stop audio sink after call to AudioEndTime() above, otherwise it will
         // return an incorrect value due to a null mAudioSink.
-        StopAudioThread();
+        StopAudioSink();
         StopDecodedStream();
       }
 
       return NS_OK;
     }
   }
 
   return NS_OK;
@@ -2434,17 +2434,17 @@ MediaDecoderStateMachine::Reset()
   MOZ_ASSERT(IsShutdown() ||
              mState == DECODER_STATE_SEEKING ||
              mState == DECODER_STATE_DORMANT ||
              mState == DECODER_STATE_DECODING_NONE);
 
   // Stop the audio thread. Otherwise, AudioSink might be accessing AudioQueue
   // outside of the decoder monitor while we are clearing the queue and causes
   // crash for no samples to be popped.
-  StopAudioThread();
+  StopAudioSink();
   StopDecodedStream();
 
   mVideoFrameEndTime = -1;
   mDecodedVideoEndTime = -1;
   mDecodedAudioEndTime = -1;
   mAudioCompleted = false;
   AudioQueue().Reset();
   VideoQueue().Reset();
@@ -3049,17 +3049,17 @@ MediaDecoderStateMachine::AudioEndTime()
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   if (mAudioSink) {
     return mAudioSink->GetEndTime();
   } else if (mAudioCaptured) {
     return mDecodedStream->AudioEndTime();
   }
   // Don't call this after mAudioSink becomes null since we can't distinguish
-  // "before StartAudioThread" and "after StopAudioThread" where mAudioSink
+  // "before StartAudioSink" and "after StopAudioSink" where mAudioSink
   // is null in both cases.
   MOZ_ASSERT(!mAudioCompleted);
   return -1;
 }
 
 void MediaDecoderStateMachine::OnAudioSinkComplete()
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -3130,17 +3130,17 @@ void MediaDecoderStateMachine::DispatchA
 {
   nsRefPtr<MediaDecoderStateMachine> self = this;
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
   {
     MOZ_ASSERT(self->OnTaskQueue());
     ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
     if (!self->mAudioCaptured) {
       // Stop the audio sink if it's running.
-      self->StopAudioThread();
+      self->StopAudioSink();
       self->mAudioCaptured = true;
       // Start DecodedStream if we are already playing. Otherwise it will be
       // handled in MaybeStartPlayback().
       if (self->IsPlaying()) {
         self->StartDecodedStream();
       }
       self->ScheduleStateMachine();
     }
@@ -3155,17 +3155,17 @@ void MediaDecoderStateMachine::DispatchA
   {
     MOZ_ASSERT(self->OnTaskQueue());
     ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
     if (self->mAudioCaptured) {
       self->StopDecodedStream();
       // Start again the audio sink.
       self->mAudioCaptured = false;
       if (self->IsPlaying()) {
-        self->StartAudioThread();
+        self->StartAudioSink();
       }
       self->ScheduleStateMachine();
     }
   });
   OwnerThread()->Dispatch(r.forget());
 }
 
 void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -94,18 +94,21 @@ hardware (via AudioStream).
 #include "MediaEventSource.h"
 #include "MediaMetadataManager.h"
 #include "MediaTimer.h"
 #include "DecodedStream.h"
 #include "ImageContainer.h"
 
 namespace mozilla {
 
+namespace media {
+class AudioSink;
+}
+
 class AudioSegment;
-class AudioSink;
 class TaskQueue;
 
 extern PRLogModuleInfo* gMediaDecoderLog;
 extern PRLogModuleInfo* gMediaSampleLog;
 
 /*
   The state machine class. This manages the decoding and seeking in the
   MediaDecoderReader on the decode task queue, and A/V sync on the shared
@@ -505,23 +508,25 @@ protected:
 
   // If we have video, display a video frame if it's time for display has
   // arrived, otherwise sleep until it's time for the next frame. Update the
   // current frame time as appropriate, and trigger ready state update.  The
   // decoder monitor must be held with exactly one lock count. Called on the
   // state machine thread.
   void UpdateRenderedVideoFrames();
 
-  // Stops the audio thread. The decoder monitor must be held with exactly
-  // one lock count. Called on the state machine thread.
-  void StopAudioThread();
+  // Stops the audio sink and shut it down.
+  // The decoder monitor must be held with exactly one lock count.
+  // Called on the state machine thread.
+  void StopAudioSink();
 
-  // Starts the audio thread. The decoder monitor must be held with exactly
-  // one lock count. Called on the state machine thread.
-  void StartAudioThread();
+  // Create and start the audio sink.
+  // The decoder monitor must be held with exactly one lock count.
+  // Called on the state machine thread.
+  void StartAudioSink();
 
   void StopDecodedStream();
 
   void StartDecodedStream();
 
   // Notification method invoked when mPlayState changes.
   void PlayStateChanged();
 
@@ -992,17 +997,17 @@ private:
 
   // The position that we're currently seeking to.
   SeekJob mCurrentSeek;
 
   // Media Fragment end time in microseconds. Access controlled by decoder monitor.
   int64_t mFragmentEndTime;
 
   // The audio sink resource.  Used on state machine and audio threads.
-  RefPtr<AudioSink> mAudioSink;
+  RefPtr<media::AudioSink> mAudioSink;
 
   // The reader, don't call its methods with the decoder monitor held.
   // This is created in the state machine's constructor.
   nsRefPtr<MediaDecoderReader> mReader;
 
   // The end time of the last audio frame that's been pushed onto the audio sink
   // or DecodedStream in microseconds. This will approximately be the end time
   // of the audio stream, unless another frame is pushed to the hardware.
new file mode 100644
--- /dev/null
+++ b/dom/media/mediasink/AudioSink.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#if !defined(AudioSink_h__)
+#define AudioSink_h__
+
+#include "mozilla/nsRefPtr.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+
+class MediaData;
+template <class T> class MediaQueue;
+
+namespace media {
+
+/*
+ * Define basic APIs for derived class instance to operate or obtain
+ * information from it.
+ */
+class AudioSink {
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioSink)
+  AudioSink(MediaQueue<MediaData>& aAudioQueue)
+    : mAudioQueue(aAudioQueue)
+  {}
+
+  // Return a promise which will be resolved when AudioSink finishes playing,
+  // or rejected if any error.
+  virtual nsRefPtr<GenericPromise> Init() = 0;
+
+  virtual int64_t GetEndTime() const = 0;
+  virtual int64_t GetPosition() = 0;
+
+  // Check whether we've pushed more frames to the audio
+  // hardware than it has played.
+  virtual bool HasUnplayedFrames() = 0;
+
+  // Shut down the AudioSink's resources.
+  virtual void Shutdown() = 0;
+
+  // Change audio playback setting.
+  virtual void SetVolume(double aVolume) = 0;
+  virtual void SetPlaybackRate(double aPlaybackRate) = 0;
+  virtual void SetPreservesPitch(bool aPreservesPitch) = 0;
+
+  // Change audio playback status pause/resume.
+  virtual void SetPlaying(bool aPlaying) = 0;
+
+protected:
+  virtual ~AudioSink() {}
+
+  virtual MediaQueue<MediaData>& AudioQueue() const {
+    return mAudioQueue;
+  }
+
+  // To queue audio data (no matter it's plain or encoded or encrypted, depends
+  // on the subclass)
+  MediaQueue<MediaData>& mAudioQueue;
+};
+
+} // namespace media
+} // namespace mozilla
+
+#endif
rename from dom/media/AudioSink.cpp
rename to dom/media/mediasink/DecodedAudioDataSink.cpp
--- a/dom/media/AudioSink.cpp
+++ b/dom/media/mediasink/DecodedAudioDataSink.cpp
@@ -1,171 +1,175 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "AudioSink.h"
 #include "AudioStream.h"
 #include "MediaQueue.h"
+#include "DecodedAudioDataSink.h"
 #include "VideoUtils.h"
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/DebugOnly.h"
 
 namespace mozilla {
 
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define SINK_LOG(msg, ...) \
-  MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("AudioSink=%p " msg, this, ##__VA_ARGS__))
+  MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, \
+    ("DecodedAudioDataSink=%p " msg, this, ##__VA_ARGS__))
 #define SINK_LOG_V(msg, ...) \
-  MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, ("AudioSink=%p " msg, this, ##__VA_ARGS__))
+  MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, \
+  ("DecodedAudioDataSink=%p " msg, this, ##__VA_ARGS__))
+
+namespace media {
 
 // The amount of audio frames that is used to fuzz rounding errors.
 static const int64_t AUDIO_FUZZ_FRAMES = 1;
 
-AudioSink::AudioSink(MediaQueue<MediaData>& aAudioQueue,
-                     int64_t aStartTime,
-                     const AudioInfo& aInfo,
-                     dom::AudioChannel aChannel)
-  : mAudioQueue(aAudioQueue)
-  , mMonitor("AudioSink::mMonitor")
+DecodedAudioDataSink::DecodedAudioDataSink(MediaQueue<MediaData>& aAudioQueue,
+                                           int64_t aStartTime,
+                                           const AudioInfo& aInfo,
+                                           dom::AudioChannel aChannel)
+  : AudioSink(aAudioQueue)
+  , mMonitor("DecodedAudioDataSink::mMonitor")
   , mState(AUDIOSINK_STATE_INIT)
   , mAudioLoopScheduled(false)
   , mStartTime(aStartTime)
   , mWritten(0)
   , mLastGoodPosition(0)
   , mInfo(aInfo)
   , mChannel(aChannel)
   , mStopAudioThread(false)
   , mPlaying(true)
 {
 }
 
 void
-AudioSink::SetState(State aState)
+DecodedAudioDataSink::SetState(State aState)
 {
   AssertOnAudioThread();
   mPendingState = Some(aState);
 }
 
 void
-AudioSink::DispatchTask(already_AddRefed<nsIRunnable>&& event)
+DecodedAudioDataSink::DispatchTask(already_AddRefed<nsIRunnable>&& event)
 {
   DebugOnly<nsresult> rv = mThread->Dispatch(Move(event), NS_DISPATCH_NORMAL);
   // There isn't much we can do if Dispatch() fails.
   // Just assert it to keep things simple.
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
 
 void
-AudioSink::OnAudioQueueEvent()
+DecodedAudioDataSink::OnAudioQueueEvent()
 {
   AssertOnAudioThread();
   if (!mAudioLoopScheduled) {
     AudioLoop();
   }
 }
 
 void
-AudioSink::ConnectListener()
+DecodedAudioDataSink::ConnectListener()
 {
   AssertOnAudioThread();
   mPushListener = AudioQueue().PushEvent().Connect(
-    mThread, this, &AudioSink::OnAudioQueueEvent);
+    mThread, this, &DecodedAudioDataSink::OnAudioQueueEvent);
   mFinishListener = AudioQueue().FinishEvent().Connect(
-    mThread, this, &AudioSink::OnAudioQueueEvent);
+    mThread, this, &DecodedAudioDataSink::OnAudioQueueEvent);
 }
 
 void
-AudioSink::DisconnectListener()
+DecodedAudioDataSink::DisconnectListener()
 {
   AssertOnAudioThread();
   mPushListener.Disconnect();
   mFinishListener.Disconnect();
 }
 
 void
-AudioSink::ScheduleNextLoop()
+DecodedAudioDataSink::ScheduleNextLoop()
 {
   AssertOnAudioThread();
   if (mAudioLoopScheduled) {
     return;
   }
   mAudioLoopScheduled = true;
-  nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &AudioSink::AudioLoop);
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &DecodedAudioDataSink::AudioLoop);
   DispatchTask(r.forget());
 }
 
 void
-AudioSink::ScheduleNextLoopCrossThread()
+DecodedAudioDataSink::ScheduleNextLoopCrossThread()
 {
   AssertNotOnAudioThread();
-  nsRefPtr<AudioSink> self = this;
+  nsRefPtr<DecodedAudioDataSink> self = this;
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () {
     // Do nothing if there is already a pending task waiting for its turn.
     if (!self->mAudioLoopScheduled) {
       self->AudioLoop();
     }
   });
   DispatchTask(r.forget());
 }
 
 nsRefPtr<GenericPromise>
-AudioSink::Init()
+DecodedAudioDataSink::Init()
 {
   nsRefPtr<GenericPromise> p = mEndPromise.Ensure(__func__);
   nsresult rv = NS_NewNamedThread("Media Audio",
                                   getter_AddRefs(mThread),
                                   nullptr,
                                   SharedThreadPool::kStackSize);
   if (NS_FAILED(rv)) {
     mEndPromise.Reject(rv, __func__);
     return p;
   }
 
   ScheduleNextLoopCrossThread();
   return p;
 }
 
 int64_t
-AudioSink::GetPosition()
+DecodedAudioDataSink::GetPosition()
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
 
   int64_t pos;
   if (mAudioStream &&
       (pos = mAudioStream->GetPosition()) >= 0) {
     // Update the last good position when we got a good one.
     mLastGoodPosition = pos;
   }
 
   return mStartTime + mLastGoodPosition;
 }
 
 bool
-AudioSink::HasUnplayedFrames()
+DecodedAudioDataSink::HasUnplayedFrames()
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   // Experimentation suggests that GetPositionInFrames() is zero-indexed,
   // so we need to add 1 here before comparing it to mWritten.
   return mAudioStream && mAudioStream->GetPositionInFrames() + 1 < mWritten;
 }
 
 void
-AudioSink::Shutdown()
+DecodedAudioDataSink::Shutdown()
 {
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     if (mAudioStream) {
       mAudioStream->Cancel();
     }
   }
-  nsRefPtr<AudioSink> self = this;
+  nsRefPtr<DecodedAudioDataSink> self = this;
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
     self->mStopAudioThread = true;
     if (!self->mAudioLoopScheduled) {
       self->AudioLoop();
     }
   });
   DispatchTask(r.forget());
 
@@ -179,60 +183,60 @@ AudioSink::Shutdown()
   // Should've reached the final state after shutdown.
   MOZ_ASSERT(mState == AUDIOSINK_STATE_SHUTDOWN ||
              mState == AUDIOSINK_STATE_ERROR);
   // Should have no pending state change.
   MOZ_ASSERT(mPendingState.isNothing());
 }
 
 void
-AudioSink::SetVolume(double aVolume)
+DecodedAudioDataSink::SetVolume(double aVolume)
 {
   AssertNotOnAudioThread();
-  nsRefPtr<AudioSink> self = this;
+  nsRefPtr<DecodedAudioDataSink> self = this;
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
     if (self->mState == AUDIOSINK_STATE_PLAYING) {
       self->mAudioStream->SetVolume(aVolume);
     }
   });
   DispatchTask(r.forget());
 }
 
 void
-AudioSink::SetPlaybackRate(double aPlaybackRate)
+DecodedAudioDataSink::SetPlaybackRate(double aPlaybackRate)
 {
   AssertNotOnAudioThread();
   MOZ_ASSERT(aPlaybackRate != 0, "Don't set the playbackRate to 0 on AudioStream");
-  nsRefPtr<AudioSink> self = this;
+  nsRefPtr<DecodedAudioDataSink> self = this;
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
     if (self->mState == AUDIOSINK_STATE_PLAYING) {
       self->mAudioStream->SetPlaybackRate(aPlaybackRate);
     }
   });
   DispatchTask(r.forget());
 }
 
 void
-AudioSink::SetPreservesPitch(bool aPreservesPitch)
+DecodedAudioDataSink::SetPreservesPitch(bool aPreservesPitch)
 {
   AssertNotOnAudioThread();
-  nsRefPtr<AudioSink> self = this;
+  nsRefPtr<DecodedAudioDataSink> self = this;
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
     if (self->mState == AUDIOSINK_STATE_PLAYING) {
       self->mAudioStream->SetPreservesPitch(aPreservesPitch);
     }
   });
   DispatchTask(r.forget());
 }
 
 void
-AudioSink::SetPlaying(bool aPlaying)
+DecodedAudioDataSink::SetPlaying(bool aPlaying)
 {
   AssertNotOnAudioThread();
-  nsRefPtr<AudioSink> self = this;
+  nsRefPtr<DecodedAudioDataSink> self = this;
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
     if (self->mState != AUDIOSINK_STATE_PLAYING ||
         self->mPlaying == aPlaying) {
       return;
     }
     self->mPlaying = aPlaying;
     // pause/resume AudioStream as necessary.
     if (!aPlaying && !self->mAudioStream->IsPaused()) {
@@ -244,17 +248,17 @@ AudioSink::SetPlaying(bool aPlaying)
     if (aPlaying && !self->mAudioLoopScheduled) {
       self->AudioLoop();
     }
   });
   DispatchTask(r.forget());
 }
 
 nsresult
-AudioSink::InitializeAudioStream()
+DecodedAudioDataSink::InitializeAudioStream()
 {
   // AudioStream initialization can block for extended periods in unusual
   // circumstances, so we take care to drop the decoder monitor while
   // initializing.
   RefPtr<AudioStream> audioStream(new AudioStream());
   nsresult rv = audioStream->Init(mInfo.mChannels, mInfo.mRate,
                                   mChannel, AudioStream::HighLatency);
   if (NS_FAILED(rv)) {
@@ -264,69 +268,69 @@ AudioSink::InitializeAudioStream()
 
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   mAudioStream = audioStream;
 
   return NS_OK;
 }
 
 void
-AudioSink::Drain()
+DecodedAudioDataSink::Drain()
 {
   AssertOnAudioThread();
   MOZ_ASSERT(mPlaying && !mAudioStream->IsPaused());
   // If the media was too short to trigger the start of the audio stream,
   // start it now.
   mAudioStream->Start();
   mAudioStream->Drain();
 }
 
 void
-AudioSink::Cleanup()
+DecodedAudioDataSink::Cleanup()
 {
   AssertOnAudioThread();
   mEndPromise.Resolve(true, __func__);
   // Since the promise if resolved asynchronously, we don't shutdown
   // AudioStream here so MDSM::ResyncAudioClock can get the correct
   // audio position.
 }
 
 bool
-AudioSink::ExpectMoreAudioData()
+DecodedAudioDataSink::ExpectMoreAudioData()
 {
   return AudioQueue().GetSize() == 0 && !AudioQueue().IsFinished();
 }
 
 bool
-AudioSink::WaitingForAudioToPlay()
+DecodedAudioDataSink::WaitingForAudioToPlay()
 {
   AssertOnAudioThread();
   // Return true if we're not playing, and we're not shutting down, or we're
   // playing and we've got no audio to play.
   if (!mStopAudioThread && (!mPlaying || ExpectMoreAudioData())) {
     return true;
   }
   return false;
 }
 
 bool
-AudioSink::IsPlaybackContinuing()
+DecodedAudioDataSink::IsPlaybackContinuing()
 {
   AssertOnAudioThread();
   // If we're shutting down, captured, or at EOS, break out and exit the audio
   // thread.
   if (mStopAudioThread || AudioQueue().AtEndOfStream()) {
     return false;
   }
 
   return true;
 }
 
 void
-AudioSink::AudioLoop()
+DecodedAudioDataSink::AudioLoop()
 {
   AssertOnAudioThread();
   mAudioLoopScheduled = false;
 
   switch (mState) {
     case AUDIOSINK_STATE_INIT: {
       SINK_LOG("AudioLoop started");
       nsresult rv = InitializeAudioStream();
@@ -381,17 +385,17 @@ AudioSink::AudioLoop()
     mState = mPendingState.ref();
     mPendingState.reset();
     // Schedule next loop when state changes.
     ScheduleNextLoop();
   }
 }
 
 bool
-AudioSink::PlayAudio()
+DecodedAudioDataSink::PlayAudio()
 {
   // See if there's a gap in the audio. If there is, push silence into the
   // audio hardware, so we can play across the gap.
   // Calculate the timestamp of the next chunk of audio in numbers of
   // samples.
   NS_ASSERTION(AudioQueue().GetSize() > 0, "Should have data to play");
   CheckedInt64 sampleTime = UsecsToFrames(AudioQueue().PeekFront()->mTime, mInfo.mRate);
 
@@ -415,30 +419,30 @@ AudioSink::PlayAudio()
   } else {
     mWritten += PlayFromAudioQueue();
   }
 
   return true;
 }
 
 void
-AudioSink::FinishAudioLoop()
+DecodedAudioDataSink::FinishAudioLoop()
 {
   AssertOnAudioThread();
   MOZ_ASSERT(mStopAudioThread || AudioQueue().AtEndOfStream());
   if (!mStopAudioThread && mPlaying) {
     Drain();
   }
   SINK_LOG("AudioLoop complete");
   Cleanup();
   SINK_LOG("AudioLoop exit");
 }
 
 uint32_t
-AudioSink::PlaySilence(uint32_t aFrames)
+DecodedAudioDataSink::PlaySilence(uint32_t aFrames)
 {
   // Maximum number of bytes we'll allocate and write at once to the audio
   // hardware when the audio stream contains missing frames and we're
   // writing silence in order to fill the gap. We limit our silence-writes
   // to 32KB in order to avoid allocating an impossibly large chunk of
   // memory if we encounter a large chunk of silence.
   const uint32_t SILENCE_BYTES_CHUNK = 32 * 1024;
 
@@ -447,17 +451,17 @@ AudioSink::PlaySilence(uint32_t aFrames)
   uint32_t maxFrames = SILENCE_BYTES_CHUNK / mInfo.mChannels / sizeof(AudioDataValue);
   uint32_t frames = std::min(aFrames, maxFrames);
   SINK_LOG_V("playing %u frames of silence", aFrames);
   WriteSilence(frames);
   return frames;
 }
 
 uint32_t
-AudioSink::PlayFromAudioQueue()
+DecodedAudioDataSink::PlayFromAudioQueue()
 {
   AssertOnAudioThread();
   NS_ASSERTION(!mAudioStream->IsPaused(), "Don't play when paused");
   nsRefPtr<AudioData> audio =
     dont_AddRef(AudioQueue().PopFront().take()->As<AudioData>());
 
   SINK_LOG_V("playing %u frames of audio at time %lld",
              audio->mFrames, audio->mTime);
@@ -470,56 +474,57 @@ AudioSink::PlayFromAudioQueue()
   }
 
   StartAudioStreamPlaybackIfNeeded();
 
   return audio->mFrames;
 }
 
 void
-AudioSink::StartAudioStreamPlaybackIfNeeded()
+DecodedAudioDataSink::StartAudioStreamPlaybackIfNeeded()
 {
   // This value has been chosen empirically.
   const uint32_t MIN_WRITE_BEFORE_START_USECS = 200000;
 
   // We want to have enough data in the buffer to start the stream.
   if (static_cast<double>(mAudioStream->GetWritten()) / mAudioStream->GetRate() >=
       static_cast<double>(MIN_WRITE_BEFORE_START_USECS) / USECS_PER_S) {
     mAudioStream->Start();
   }
 }
 
 void
-AudioSink::WriteSilence(uint32_t aFrames)
+DecodedAudioDataSink::WriteSilence(uint32_t aFrames)
 {
   uint32_t numSamples = aFrames * mInfo.mChannels;
   nsAutoTArray<AudioDataValue, 1000> buf;
   buf.SetLength(numSamples);
   memset(buf.Elements(), 0, numSamples * sizeof(AudioDataValue));
   mAudioStream->Write(buf.Elements(), aFrames);
 
   StartAudioStreamPlaybackIfNeeded();
 }
 
 int64_t
-AudioSink::GetEndTime() const
+DecodedAudioDataSink::GetEndTime() const
 {
   CheckedInt64 playedUsecs = FramesToUsecs(mWritten, mInfo.mRate) + mStartTime;
   if (!playedUsecs.isValid()) {
     NS_WARNING("Int overflow calculating audio end time");
     return -1;
   }
   return playedUsecs.value();
 }
 
 void
-AudioSink::AssertOnAudioThread()
+DecodedAudioDataSink::AssertOnAudioThread()
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mThread);
 }
 
 void
-AudioSink::AssertNotOnAudioThread()
+DecodedAudioDataSink::AssertNotOnAudioThread()
 {
   MOZ_ASSERT(NS_GetCurrentThread() != mThread);
 }
 
+} // namespace media
 } // namespace mozilla
rename from dom/media/AudioSink.h
rename to dom/media/mediasink/DecodedAudioDataSink.h
--- a/dom/media/AudioSink.h
+++ b/dom/media/mediasink/DecodedAudioDataSink.h
@@ -1,73 +1,70 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-#if !defined(AudioSink_h__)
-#define AudioSink_h__
+#if !defined(DecodedAudioDataSink_h__)
+#define DecodedAudioDataSink_h__
 
+#include "AudioSink.h"
 #include "MediaInfo.h"
 #include "mozilla/nsRefPtr.h"
 #include "nsISupportsImpl.h"
 
 #include "mozilla/dom/AudioChannelBinding.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/ReentrantMonitor.h"
 
 namespace mozilla {
+namespace media {
 
-class AudioData;
-class AudioStream;
-template <class T> class MediaQueue;
-
-class AudioSink {
+class DecodedAudioDataSink : public AudioSink {
 public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioSink)
 
-  AudioSink(MediaQueue<MediaData>& aAudioQueue,
-            int64_t aStartTime,
-            const AudioInfo& aInfo,
-            dom::AudioChannel aChannel);
+  DecodedAudioDataSink(MediaQueue<MediaData>& aAudioQueue,
+                       int64_t aStartTime,
+                       const AudioInfo& aInfo,
+                       dom::AudioChannel aChannel);
 
-  // Return a promise which will be resolved when AudioSink finishes playing,
-  // or rejected if any error.
-  nsRefPtr<GenericPromise> Init();
+  // Return a promise which will be resolved when DecodedAudioDataSink
+  // finishes playing, or rejected if any error.
+  nsRefPtr<GenericPromise> Init() override;
 
   /*
    * All public functions below are thread-safe.
    */
-  int64_t GetPosition();
-  int64_t GetEndTime() const;
+  int64_t GetPosition() override;
+  int64_t GetEndTime() const override;
 
   // Check whether we've pushed more frames to the audio hardware than it has
   // played.
-  bool HasUnplayedFrames();
+  bool HasUnplayedFrames() override;
 
-  // Shut down the AudioSink's resources.
-  void Shutdown();
+  // Shut down the DecodedAudioDataSink's resources.
+  void Shutdown() override;
 
-  void SetVolume(double aVolume);
-  void SetPlaybackRate(double aPlaybackRate);
-  void SetPreservesPitch(bool aPreservesPitch);
-  void SetPlaying(bool aPlaying);
+  void SetVolume(double aVolume) override;
+  void SetPlaybackRate(double aPlaybackRate) override;
+  void SetPreservesPitch(bool aPreservesPitch) override;
+  void SetPlaying(bool aPlaying) override;
 
 private:
   enum State {
     AUDIOSINK_STATE_INIT,
     AUDIOSINK_STATE_PLAYING,
     AUDIOSINK_STATE_COMPLETE,
     AUDIOSINK_STATE_SHUTDOWN,
     AUDIOSINK_STATE_ERROR
   };
 
-  ~AudioSink() {}
+  virtual ~DecodedAudioDataSink() {}
 
   void DispatchTask(already_AddRefed<nsIRunnable>&& event);
   void SetState(State aState);
   void ScheduleNextLoop();
   void ScheduleNextLoopCrossThread();
 
   void OnAudioQueueEvent();
   void ConnectListener();
@@ -114,32 +111,27 @@ private:
   // audio data to the audio hardware.  Called on the audio thread.
   uint32_t PlayFromAudioQueue();
 
   // If we have already written enough frames to the AudioStream, start the
   // playback.
   void StartAudioStreamPlaybackIfNeeded();
   void WriteSilence(uint32_t aFrames);
 
-  MediaQueue<MediaData>& AudioQueue() const {
-    return mAudioQueue;
-  }
-
   ReentrantMonitor& GetReentrantMonitor() const {
     return mMonitor;
   }
 
   void AssertCurrentThreadInMonitor() const {
     GetReentrantMonitor().AssertCurrentThreadIn();
   }
 
   void AssertOnAudioThread();
   void AssertNotOnAudioThread();
 
-  MediaQueue<MediaData>& mAudioQueue;
   mutable ReentrantMonitor mMonitor;
 
   // There members are accessed on the audio thread only.
   State mState;
   Maybe<State> mPendingState;
   bool mAudioLoopScheduled;
 
   // Thread for pushing audio onto the audio hardware.
@@ -163,23 +155,24 @@ private:
 
   // Keep the last good position returned from the audio stream. Used to ensure
   // position returned by GetPosition() is mono-increasing in spite of audio
   // stream error.
   int64_t mLastGoodPosition;
 
   const AudioInfo mInfo;
 
-  dom::AudioChannel mChannel;
+  const dom::AudioChannel mChannel;
 
   bool mStopAudioThread;
 
   bool mPlaying;
 
   MozPromiseHolder<GenericPromise> mEndPromise;
 
   MediaEventListener mPushListener;
   MediaEventListener mFinishListener;
 };
 
+} // namespace media
 } // namespace mozilla
 
 #endif
new file mode 100644
--- /dev/null
+++ b/dom/media/mediasink/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+    'DecodedAudioDataSink.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+FAIL_ON_WARNINGS = True
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -20,16 +20,17 @@ with Files('GetUserMedia*'):
     BUG_COMPONENT = component_av
 
 DIRS += [
     'encoder',
     'gmp',
     'gmp-plugin',
     'gmp-plugin-openh264',
     'imagecapture',
+    'mediasink',
     'mediasource',
     'ogg',
     'platforms',
     'systemservices',
     'webaudio',
     'webrtc',
     'webspeech',
     'webvtt',
@@ -188,17 +189,16 @@ EXPORTS.mozilla.dom += [
     'VideoTrackList.h',
 ]
 
 UNIFIED_SOURCES += [
     'AudioCaptureStream.cpp',
     'AudioChannelFormat.cpp',
     'AudioCompactor.cpp',
     'AudioSegment.cpp',
-    'AudioSink.cpp',
     'AudioStream.cpp',
     'AudioStreamTrack.cpp',
     'AudioTrack.cpp',
     'AudioTrackList.cpp',
     'CanvasCaptureMediaStream.cpp',
     'CubebUtils.cpp',
     'DecodedStream.cpp',
     'DOMMediaStream.cpp',
--- a/dom/messagechannel/MessagePortChild.cpp
+++ b/dom/messagechannel/MessagePortChild.cpp
@@ -18,26 +18,28 @@ MessagePortChild::RecvStopSendingDataCon
   mPort->StopSendingDataConfirmed();
   MOZ_ASSERT(!mPort);
   return true;
 }
 
 bool
 MessagePortChild::RecvEntangled(nsTArray<MessagePortMessage>&& aMessages)
 {
-  MOZ_ASSERT(mPort);
-  mPort->Entangled(aMessages);
+  if (mPort) {
+    mPort->Entangled(aMessages);
+  }
   return true;
 }
 
 bool
 MessagePortChild::RecvReceiveData(nsTArray<MessagePortMessage>&& aMessages)
 {
-  MOZ_ASSERT(mPort);
-  mPort->MessagesReceived(aMessages);
+  if (mPort) {
+    mPort->MessagesReceived(aMessages);
+  }
   return true;
 }
 
 void
 MessagePortChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   if (mPort) {
     mPort->Closed();
--- a/dom/plugins/test/unit/xpcshell.ini
+++ b/dom/plugins/test/unit/xpcshell.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 skip-if = buildapp == 'mulet' || toolkit == 'android' || toolkit == 'gonk'
 head = head_plugins.js
 tail = 
+tags = addons
 
 [test_allowed_types.js]
 skip-if = appname == "thunderbird"
 reason = plugins are disabled by default in Thunderbird
 [test_bug455213.js]
 # Bug 676953: test fails consistently on Android
 fail-if = os == "android"
 [test_bug471245.js]
--- a/dom/requestsync/RequestSyncService.jsm
+++ b/dom/requestsync/RequestSyncService.jsm
@@ -71,16 +71,20 @@ this.RequestSyncService = {
   _wifi: false,
 
   _activeTask: null,
   _queuedTasks: [],
 
   _timers: {},
   _pendingRequests: {},
 
+  // This array contains functions to be executed after the scheduling of the
+  // current task or immediately if there are not scheduling in progress.
+  _afterSchedulingTasks: [],
+
   // Initialization of the RequestSyncService.
   init: function() {
     debug("init");
 
     this._messages.forEach((function(msgName) {
       ppmm.addMessageListener(msgName, this);
     }).bind(this));
 
@@ -89,26 +93,24 @@ this.RequestSyncService = {
     Services.obs.addObserver(this, 'wifi-state-changed', false);
 
     this.initDBHelper("requestSync", RSYNCDB_VERSION, [RSYNCDB_NAME]);
 
     // Loading all the data from the database into the _registrations map.
     // Any incoming message will be stored and processed when the async
     // operation is completed.
 
-    let self = this;
     this.dbTxn("readonly", function(aStore) {
       aStore.openCursor().onsuccess = function(event) {
         let cursor = event.target.result;
         if (cursor) {
-          self.addRegistration(cursor.value);
-          cursor.continue();
+          this.addRegistration(cursor.value, cursor.continue);
         }
       }
-    },
+    }.bind(this),
     function() {
       debug("initialization done");
     },
     function() {
       dump("ERROR!! RequestSyncService - Failed to retrieve data from the database.\n");
     });
   },
 
@@ -122,37 +124,42 @@ this.RequestSyncService = {
 
     Services.obs.removeObserver(this, 'xpcom-shutdown');
     Services.obs.removeObserver(this, 'clear-origin-data');
     Services.obs.removeObserver(this, 'wifi-state-changed');
 
     this.close();
 
     // Removing all the registrations will delete the pending timers.
-    let self = this;
     this.forEachRegistration(function(aObj) {
-      let key = self.principalToKey(aObj.principal);
-      self.removeRegistrationInternal(aObj.data.task, key);
-    });
+      let key = this.principalToKey(aObj.principal);
+      this.removeRegistrationInternal(aObj.data.task, key);
+    }.bind(this));
   },
 
   observe: function(aSubject, aTopic, aData) {
     debug("observe");
 
     switch (aTopic) {
       case 'xpcom-shutdown':
-        this.shutdown();
+        this.executeAfterScheduling(function() {
+          this.shutdown();
+        }.bind(this));
         break;
 
       case 'clear-origin-data':
-        this.clearData(aData);
+        this.executeAfterScheduling(function() {
+          this.clearData(aData);
+        }.bind(this));
         break;
 
       case 'wifi-state-changed':
-        this.wifiStateChanged(aSubject == 'enabled');
+        this.executeAfterScheduling(function() {
+          this.wifiStateChanged(aSubject == 'enabled');
+        }.bind(this));
         break;
 
       default:
         debug("Wrong observer topic: " + aTopic);
         break;
     }
   },
 
@@ -203,26 +210,30 @@ this.RequestSyncService = {
   },
 
   // This method generates the key for the indexedDB object storage.
   principalToKey: function(aPrincipal) {
     return aPrincipal.origin;
   },
 
   // Add a task to the _registrations map and create the timer if it's needed.
-  addRegistration: function(aObj) {
+  addRegistration: function(aObj, aCb) {
     debug('addRegistration');
 
     let key = this.principalToKey(aObj.principal);
     if (!(key in this._registrations)) {
       this._registrations[key] = {};
     }
 
-    this.scheduleTimer(aObj);
-    this._registrations[key][aObj.data.task] = aObj;
+    this.scheduleTimer(aObj, function() {
+      this._registrations[key][aObj.data.task] = aObj;
+      if (aCb) {
+        aCb();
+      }
+    }.bind(this));
   },
 
   // Remove a task from the _registrations map and delete the timer if it's
   // needed. It also checks if the principal is correct before doing the real
   // operation.
   removeRegistration: function(aTaskName, aKey, aPrincipal) {
     debug('removeRegistration');
 
@@ -284,41 +295,55 @@ this.RequestSyncService = {
     // The principal is used to validate the message.
     let principal = aMessage.principal;
     if (!principal) {
       return;
     }
 
     switch (aMessage.name) {
       case "RequestSync:Register":
-        this.register(aMessage.target, aMessage.data, principal);
+        this.executeAfterScheduling(function() {
+          this.register(aMessage.target, aMessage.data, principal);
+        }.bind(this));
         break;
 
       case "RequestSync:Unregister":
-        this.unregister(aMessage.target, aMessage.data, principal);
+        this.executeAfterScheduling(function() {
+          this.unregister(aMessage.target, aMessage.data, principal);
+        }.bind(this));
         break;
 
       case "RequestSync:Registrations":
-        this.registrations(aMessage.target, aMessage.data, principal);
+        this.executeAfterScheduling(function() {
+          this.registrations(aMessage.target, aMessage.data, principal);
+        }.bind(this));
         break;
 
       case "RequestSync:Registration":
-        this.registration(aMessage.target, aMessage.data, principal);
+        this.executeAfterScheduling(function() {
+          this.registration(aMessage.target, aMessage.data, principal);
+        }.bind(this));
         break;
 
       case "RequestSyncManager:Registrations":
-        this.managerRegistrations(aMessage.target, aMessage.data, principal);
+        this.executeAfterScheduling(function() {
+          this.managerRegistrations(aMessage.target, aMessage.data, principal);
+        }.bind(this));
         break;
 
       case "RequestSyncManager:SetPolicy":
-        this.managerSetPolicy(aMessage.target, aMessage.data, principal);
+        this.executeAfterScheduling(function() {
+          this.managerSetPolicy(aMessage.target, aMessage.data, principal);
+        }.bind(this));
         break;
 
       case "RequestSyncManager:RunTask":
-        this.managerRunTask(aMessage.target, aMessage.data, principal);
+        this.executeAfterScheduling(function() {
+          this.managerRunTask(aMessage.target, aMessage.data, principal);
+        }.bind(this));
         break;
 
       default:
         debug("Wrong message: " + aMessage.name);
         break;
     }
   },
 
@@ -384,19 +409,20 @@ this.RequestSyncService = {
                  data: aData.params,
                  active: true };
 
     let self = this;
     this.dbTxn('readwrite', function(aStore) {
       aStore.put(data, data.dbKey);
     },
     function() {
-      self.addRegistration(data);
-      aTarget.sendAsyncMessage("RequestSync:Register:Return",
-                               { requestID: aData.requestID });
+      self.addRegistration(data, function() {
+        aTarget.sendAsyncMessage("RequestSync:Register:Return",
+                                 { requestID: aData.requestID });
+      });
     },
     function() {
       aTarget.sendAsyncMessage("RequestSync:Register:Return",
                                { requestID: aData.requestID,
                                  error: "IndexDBError" } );
     });
   },
 
@@ -519,19 +545,20 @@ this.RequestSyncService = {
 
     if (!toSave) {
       aTarget.sendAsyncMessage("RequestSyncManager:SetPolicy:Return",
                                { requestID: aData.requestID, error: "UnknownTaskError" });
       return;
     }
 
     this.updateObjectInDB(toSave, function() {
-      self.scheduleTimer(toSave);
-      aTarget.sendAsyncMessage("RequestSyncManager:SetPolicy:Return",
-                               { requestID: aData.requestID });
+      self.scheduleTimer(toSave, function() {
+        aTarget.sendAsyncMessage("RequestSyncManager:SetPolicy:Return",
+                                 { requestID: aData.requestID });
+      });
     });
   },
 
   // Run a task now.
   managerRunTask: function(aTarget, aData, aPrincipal) {
     debug("runTask");
 
     let task = null;
@@ -595,36 +622,67 @@ this.RequestSyncService = {
     }
 
     obj.state = aObj.state;
     obj.overwrittenMinInterval = aObj.overwrittenMinInterval;
     return obj;
   },
 
   // Creation of the timer for a particular task object.
-  scheduleTimer: function(aObj) {
+  scheduleTimer: function(aObj, aCb) {
     debug("scheduleTimer");
 
+    aCb = aCb || function() {};
+
     this.removeTimer(aObj);
 
     // A  registration can be already inactive if it was 1 shot.
     if (!aObj.active) {
+      aCb();
       return;
     }
 
     if (aObj.data.state == RSYNC_STATE_DISABLED) {
+      aCb();
       return;
     }
 
     // WifiOnly check.
     if (aObj.data.state == RSYNC_STATE_WIFIONLY && !this._wifi) {
+      aCb();
+      return;
+    }
+
+    if (this.scheduling) {
+      dump("ERROR!! RequestSyncService - ScheduleTimer called into ScheduleTimer.\n");
+      aCb();
       return;
     }
 
-    this.createTimer(aObj);
+    this.scheduling = true;
+
+    this.createTimer(aObj, function() {
+      this.scheduling = false;
+
+      while (this._afterSchedulingTasks.length) {
+        var cb = this._afterSchedulingTasks.shift();
+        cb();
+      }
+
+      aCb();
+    }.bind(this));
+  },
+
+  executeAfterScheduling: function(aCb) {
+    if (!this.scheduling) {
+      aCb();
+      return;
+    }
+
+    this._afterSchedulingTasks.push(aCb);
   },
 
   timeout: function(aObj) {
     debug("timeout");
 
     if (this._activeTask) {
       debug("queueing tasks");
       // We have an active task, let's queue this as next task.
@@ -723,27 +781,25 @@ this.RequestSyncService = {
 
     let pendingRequests = this.stealPendingRequests(this._activeTask);
     for (let i = 0; i < pendingRequests.length; ++i) {
       pendingRequests[i]
           .target.sendAsyncMessage("RequestSyncManager:RunTask:Return",
                                    { requestID: pendingRequests[i].requestID });
     }
 
-    let self = this;
     this.updateObjectInDB(this._activeTask, function() {
-      // SchedulerTimer creates a timer and a nsITimer cannot be cloned. This
-      // is the reason why this operation has to be done after storing the task
-      // into IDB.
-      if (!self._activeTask.data.oneShot) {
-        self.scheduleTimer(self._activeTask);
+      if (!this._activeTask.data.oneShot) {
+        this.scheduleTimer(this._activeTask, function() {
+          this.processNextTask();
+        }.bind(this));
+      } else {
+        this.processNextTask();
       }
-
-      self.processNextTask();
-    });
+    }.bind(this));
   },
 
   processNextTask: function() {
     debug("processNextTask");
 
     this._activeTask = null;
 
     if (this._queuedTasks.length == 0) {
@@ -831,56 +887,61 @@ this.RequestSyncService = {
 
     for (let i = 0; i < list.length; ++i) {
       aCb(list[i]);
     }
   },
 
   wifiStateChanged: function(aEnabled) {
     debug("onWifiStateChanged");
+
     this._wifi = aEnabled;
 
     if (!this._wifi) {
       // Disable all the wifiOnly tasks.
-      let self = this;
       this.forEachRegistration(function(aObj) {
-        if (aObj.data.state == RSYNC_STATE_WIFIONLY && self.hasTimer(aObj)) {
-          self.removeTimer(aObj);
+        if (aObj.data.state == RSYNC_STATE_WIFIONLY && this.hasTimer(aObj)) {
+          this.removeTimer(aObj);
 
           // It can be that this task has been already schedulated.
-          self.removeTaskFromQueue(aObj);
+          this.removeTaskFromQueue(aObj);
         }
-      });
+      }.bind(this));
       return;
     }
 
     // Enable all the tasks.
-    let self = this;
     this.forEachRegistration(function(aObj) {
-      if (aObj.active && !self.hasTimer(aObj)) {
+      if (aObj.active && !this.hasTimer(aObj)) {
         if (!aObj.data.wifiOnly) {
           dump("ERROR - Found a disabled task that is not wifiOnly.");
         }
 
-        self.scheduleTimer(aObj);
+        this.scheduleTimer(aObj);
       }
-    });
+    }.bind(this));
   },
 
-  createTimer: function(aObj) {
+  createTimer: function(aObj, aCb) {
+    aCb = aCb || function() {};
+
     let interval = aObj.data.minInterval;
     if (aObj.data.overwrittenMinInterval > 0) {
       interval = aObj.data.overwrittenMinInterval;
     }
 
     AlarmService.add(
       { date: new Date(Date.now() + interval * 1000),
         ignoreTimezone: false },
       () => this.timeout(aObj),
-      aTimerId => this._timers[aObj.dbKey] = aTimerId);
+      function(aTimerId) {
+        this._timers[aObj.dbKey] = aTimerId;
+        aCb();
+      }.bind(this),
+      () => aCb());
   },
 
   hasTimer: function(aObj) {
     return (aObj.dbKey in this._timers);
   },
 
   removeTimer: function(aObj) {
     if (aObj.dbKey in this._timers) {
--- a/dom/requestsync/tests/mochitest.ini
+++ b/dom/requestsync/tests/mochitest.ini
@@ -12,17 +12,15 @@ support-files =
 skip-if = os == "android" || toolkit == "gonk"
 [test_minInterval.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_basic.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_basic_app.html]
 skip-if = os == "android" || buildapp == 'b2g'
 [test_wakeUp.html]
-#run-if = buildapp == 'b2g' && toolkit == 'gonk'
-skip-if = true
+run-if = buildapp == 'b2g' && toolkit == 'gonk'
 [test_runNow.html]
-#run-if = buildapp == 'b2g' && toolkit == 'gonk'
-skip-if = true
+run-if = buildapp == 'b2g' && toolkit == 'gonk'
 [test_promise.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_bug1151082.html]
 skip-if = os == "android" || toolkit == "gonk"
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -58,16 +58,17 @@
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/MessagePort.h"
 #include "mozilla/dom/MessagePortBinding.h"
 #include "mozilla/dom/MessagePortList.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseDebugging.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/StructuredClone.h"
+#include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/WebCryptoCommon.h"
 #include "mozilla/dom/WorkerBinding.h"
 #include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
 #include "mozilla/dom/WorkerGlobalScopeBinding.h"
 #include "mozilla/dom/indexedDB/IDBFactory.h"
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/dom/ipc/nsIRemoteBlob.h"
 #include "mozilla/ipc/BackgroundChild.h"
@@ -2390,18 +2391,19 @@ InterfaceRequestor::GetAnyLiveTabChild()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Search our list of known TabChild objects for one that still exists.
   while (!mTabChildList.IsEmpty()) {
     nsCOMPtr<nsITabChild> tabChild =
       do_QueryReferent(mTabChildList.LastElement());
 
-    // Does this tab child still exist?  If so, return it.  We are done.
-    if (tabChild) {
+    // Does this tab child still exist?  If so, return it.  We are done.  If the
+    // PBrowser actor is no longer useful, don't bother returning this tab.
+    if (tabChild && !static_cast<TabChild*>(tabChild.get())->IsDestroyed()) {
       return tabChild.forget();
     }
 
     // Otherwise remove the stale weak reference and check the next one
     mTabChildList.RemoveElementAt(mTabChildList.Length() - 1);
   }
 
   return nullptr;
--- a/dom/workers/XMLHttpRequest.cpp
+++ b/dom/workers/XMLHttpRequest.cpp
@@ -1953,16 +1953,22 @@ XMLHttpRequest::Open(const nsACString& a
     if (!--mProxy->mOpenCount) {
       ReleaseProxy();
     }
 
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
+  // We have been released in one of the nested Open() calls.
+  if (!mProxy) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
   --mProxy->mOpenCount;
   mProxy->mIsSyncXHR = !aAsync;
 }
 
 void
 XMLHttpRequest::SetRequestHeader(const nsACString& aHeader,
                                  const nsACString& aValue, ErrorResult& aRv)
 {
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -1788,16 +1788,17 @@ void CheckIfRenderTargetViewNeedsRecreat
     int resultColor = *(int*)mapped.pData;
     deviceContext->Unmap(cpuTexture, 0);
     cpuTexture->Release();
 
     // XXX on some drivers resultColor will not have changed to
     // match the clear
     if (resultColor != 0xffffff00) {
         gfxCriticalNote << "RenderTargetViewNeedsRecreating";
+        gANGLESupportsD3D11 = false;
     }
 
     keyedMutex->ReleaseSync(0);
 
     // It seems like this may only happen when we're using the NVIDIA gpu
     CheckForAdapterMismatch(device);
 }
 
--- a/ipc/glue/ProtocolUtils.cpp
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -310,30 +310,28 @@ FatalError(const char* aProtocolName, co
 {
   ProtocolErrorBreakpoint(aMsg);
 
   nsAutoCString formattedMessage("IPDL error [");
   formattedMessage.AppendASCII(aProtocolName);
   formattedMessage.AppendLiteral("]: \"");
   formattedMessage.AppendASCII(aMsg);
   if (aIsParent) {
-    formattedMessage.AppendLiteral("\". Killing child side as a result.");
+#ifdef MOZ_CRASHREPORTER
+    // We're going to crash the parent process because at this time
+    // there's no other really nice way of getting a minidump out of
+    // this process if we're off the main thread.
+    formattedMessage.AppendLiteral("\". Intentionally crashing.");
     NS_ERROR(formattedMessage.get());
-
-    if (aOtherPid != kInvalidProcessId && aOtherPid != base::GetCurrentProcId()) {
-      ScopedProcessHandle otherProcessHandle;
-      if (base::OpenProcessHandle(aOtherPid, &otherProcessHandle.rwget())) {
-        if (!base::KillProcess(otherProcessHandle,
-                               base::PROCESS_END_KILLED_BY_USER, false)) {
-          NS_ERROR("May have failed to kill child!");
-        }
-      } else {
-        NS_ERROR("Failed to open child process when attempting kill.");
-      }
-    }
+    CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCFatalErrorProtocol"),
+                                       nsDependentCString(aProtocolName));
+    CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCFatalErrorMsg"),
+                                       nsDependentCString(aMsg));
+#endif
+    MOZ_CRASH("IPC FatalError in the parent process!");
   } else {
     formattedMessage.AppendLiteral("\". abort()ing as a result.");
     NS_RUNTIMEABORT(formattedMessage.get());
   }
 }
 
 } // namespace ipc
 } // namespace mozilla
--- a/js/ipc/CPOWTimer.cpp
+++ b/js/ipc/CPOWTimer.cpp
@@ -36,11 +36,10 @@ CPOWTimer::~CPOWTimer()
     }
 
     const int64_t endInterval = JS_Now();
     if (endInterval <= startInterval_) {
         // Do not assume monotonicity.
         return;
     }
 
-    js::PerformanceData* performance = js::GetPerformanceData(runtime);
-    performance->totalCPOWTime += endInterval - startInterval_;
+    js::AddCPOWPerformanceDelta(runtime, endInterval - startInterval_);
 }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5326,16 +5326,17 @@ BuildStackString(JSContext* cx, HandleOb
 /* Stopwatch-based CPU monitoring. */
 
 namespace js {
 
 class AutoStopwatch;
 
 // Container for performance data
 // All values are monotonic.
+// All values are updated after running to completion.
 struct PerformanceData {
     // Number of times we have spent at least 2^n consecutive
     // milliseconds executing code in this group.
     // durations[0] is increased whenever we spend at least 1 ms
     // executing code in this group
     // durations[1] whenever we spend 2ms+
     //
     // durations[i] whenever we spend 2^ims+
@@ -5392,40 +5393,78 @@ struct PerformanceData {
 struct PerformanceGroup {
 
     // Performance data for this group.
     PerformanceData data;
 
     // An id unique to this runtime.
     const uint64_t uid;
 
+    // The number of cycles spent in this group during this iteration
+    // of the event loop. Note that cycles are not a reliable measure,
+    // especially over short intervals. See Runtime.cpp for a more
+    // complete discussion on the imprecision of cycle measurement.
+    uint64_t recentCycles;
+
+    // The number of times this group has been activated during this
+    // iteration of the event loop.
+    uint64_t recentTicks;
+
+    // The number of milliseconds spent doing CPOW during this
+    // iteration of the event loop.
+    uint64_t recentCPOW;
+
+    // The current iteration of the event loop.
+    uint64_t iteration() const {
+        return iteration_;
+    }
+
     // `true` if an instance of `AutoStopwatch` is already monitoring
     // the performance of this performance group for this iteration
     // of the event loop, `false` otherwise.
-    bool hasStopwatch(uint64_t iteration) const {
-        return stopwatch_ != nullptr && iteration_ == iteration;
+    bool hasStopwatch(uint64_t it) const {
+        return stopwatch_ != nullptr && iteration_ == it;
+    }
+
+    // `true` if a specific instance of `AutoStopwatch` is already monitoring
+    // the performance of this performance group for this iteration
+    // of the event loop, `false` otherwise.
+    bool hasStopwatch(uint64_t it, const AutoStopwatch* stopwatch) const {
+        return stopwatch_ == stopwatch && iteration_ == it;
     }
 
     // Mark that an instance of `AutoStopwatch` is monitoring
     // the performance of this group for a given iteration.
-    void acquireStopwatch(uint64_t iteration, const AutoStopwatch* stopwatch) {
-        iteration_ = iteration;
+    void acquireStopwatch(uint64_t it, const AutoStopwatch* stopwatch) {
+        if (iteration_ != it) {
+            // Any data that pretends to be recent is actually bound
+            // to an older iteration and therefore stale.
+            resetRecentData();
+        }
+        iteration_ = it;
         stopwatch_ = stopwatch;
     }
 
     // Mark that no `AutoStopwatch` is monitoring the
     // performance of this group for the iteration.
-    void releaseStopwatch(uint64_t iteration, const AutoStopwatch* stopwatch) {
-        if (iteration_ != iteration)
+    void releaseStopwatch(uint64_t it, const AutoStopwatch* stopwatch) {
+        if (iteration_ != it)
             return;
 
         MOZ_ASSERT(stopwatch == stopwatch_ || stopwatch_ == nullptr);
         stopwatch_ = nullptr;
     }
 
+    // Get rid of any data that pretends to be recent.
+    void resetRecentData() {
+        recentCycles = 0;
+        recentTicks = 0;
+        recentCPOW = 0;
+    }
+
     // Refcounting. For use with mozilla::RefPtr.
     void AddRef();
     void Release();
 
     // Construct a PerformanceGroup for a single compartment.
     explicit PerformanceGroup(JSRuntime* rt);
 
     // Construct a PerformanceGroup for a group of compartments.
@@ -5443,20 +5482,19 @@ private:
 
     // The current iteration of the event loop. If necessary,
     // may safely overflow.
     uint64_t iteration_;
 
     // The hash key for this PerformanceGroup.
     void* const key_;
 
-    // A reference counter.
+    // Refcounter.
     uint64_t refCount_;
 
-
     // `true` if this PerformanceGroup may be shared by several
     // compartments, `false` if it is dedicated to a single
     // compartment.
     const bool isSharedGroup_;
 };
 
 //
 // Each PerformanceGroupHolder handles:
@@ -5506,22 +5544,29 @@ struct PerformanceGroupHolder {
     // The PerformanceGroups held by this object.
     // Initially set to `nullptr` until the first call to `getGroup`.
     // May be reset to `nullptr` by a call to `unlink`.
     mozilla::RefPtr<js::PerformanceGroup> sharedGroup_;
     mozilla::RefPtr<js::PerformanceGroup> ownGroup_;
 };
 
 /**
- * Reset any stopwatch currently measuring.
+ * Commit any Performance Monitoring data.
  *
- * This function is designed to be called when we process a new event.
+ * Until `FlushMonitoring` has been called, all PerformanceMonitoring data is invisible
+ * to the outside world and can cancelled with a call to `ResetMonitoring`.
  */
 extern JS_PUBLIC_API(void)
-ResetStopwatches(JSRuntime*);
+FlushPerformanceMonitoring(JSRuntime*);
+
+/**
+ * Cancel any measurement that hasn't been committed.
+ */
+extern JS_PUBLIC_API(void)
+ResetPerformanceMonitoring(JSRuntime*);
 
 /**
  * Turn on/off stopwatch-based CPU monitoring.
  *
  * `SetStopwatchIsMonitoringCPOW` or `SetStopwatchIsMonitoringJank`
  * may return `false` if monitoring could not be activated, which may
  * happen if we are out of memory.
  */
@@ -5536,21 +5581,27 @@ GetStopwatchIsMonitoringJank(JSRuntime*)
 extern JS_PUBLIC_API(bool)
 SetStopwatchIsMonitoringPerCompartment(JSRuntime*, bool);
 extern JS_PUBLIC_API(bool)
 GetStopwatchIsMonitoringPerCompartment(JSRuntime*);
 
 extern JS_PUBLIC_API(bool)
 IsStopwatchActive(JSRuntime*);
 
+// Extract the CPU rescheduling data.
+extern JS_PUBLIC_API(void)
+GetPerfMonitoringTestCpuRescheduling(JSRuntime*, uint64_t* stayed, uint64_t* moved);
+
+
 /**
- * Access the performance information stored in a compartment.
+ * Add a number of microseconds to the time spent waiting on CPOWs
+ * since process start.
  */
-extern JS_PUBLIC_API(PerformanceData*)
-GetPerformanceData(JSRuntime*);
+extern JS_PUBLIC_API(void)
+AddCPOWPerformanceDelta(JSRuntime*, uint64_t delta);
 
 typedef bool
 (PerformanceStatsWalker)(JSContext* cx,
                          const PerformanceData& stats, uint64_t uid,
                          const uint64_t* parentId, void* closure);
 
 /**
  * Extract the performance statistics.
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -51,24 +51,20 @@
 #include "jit/AtomicOperations-inl.h"
 #include "jit/JitFrames-inl.h"
 #include "vm/Debugger-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/Probes-inl.h"
 #include "vm/ScopeObject-inl.h"
 #include "vm/Stack-inl.h"
 
-#if defined(XP_MACOSX)
-#include <mach/mach.h>
-#elif defined(XP_UNIX)
-#include <sys/resource.h>
-#elif defined(XP_WIN)
+#if defined(XP_WIN)
 #include <processthreadsapi.h>
 #include <windows.h>
-#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
+#endif // defined(XP_WIN)
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::ArrayLength;
 using mozilla::DebugOnly;
 using mozilla::NumberEqualsInt32;
 using mozilla::PodCopy;
@@ -390,142 +386,172 @@ class AutoStopwatch final
     uint64_t iteration_;
 
     // `true` if we are monitoring jank, `false` otherwise.
     bool isMonitoringJank_;
     // `true` if we are monitoring CPOW, `false` otherwise.
     bool isMonitoringCPOW_;
 
     // Timestamps captured while starting the stopwatch.
-    uint64_t userTimeStart_;
-    uint64_t systemTimeStart_;
+    uint64_t cyclesStart_;
     uint64_t CPOWTimeStart_;
 
-   // The performance group shared by this compartment and possibly
-   // others, or `nullptr` if another AutoStopwatch is already in
-   // charge of monitoring that group.
-   mozilla::RefPtr<js::PerformanceGroup> sharedGroup_;
-
-   // The toplevel group, representing the entire process, or `nullptr`
-   // if another AutoStopwatch is already in charge of monitoring that group.
-   mozilla::RefPtr<js::PerformanceGroup> topGroup_;
-
-   // The performance group specific to this compartment, or
-   // `nullptr` if another AutoStopwatch is already in charge of
-   // monitoring that group.
-   mozilla::RefPtr<js::PerformanceGroup> ownGroup_;
-
-   public:
+    // The CPU on which we started the measure. Defined only
+    // if `isMonitoringJank_` is `true`.
+#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA
+    struct cpuid_t {
+        WORD group_;
+        BYTE number_;
+        cpuid_t(WORD group, BYTE number)
+          : group_(group),
+            number_(number)
+        { }
+        cpuid_t()
+          : group_(0),
+            number_(0)
+        { }
+    };
+#elif defined(XP_LINUX)
+    typedef int cpuid_t;
+#else
+    typedef struct {} cpuid_t;
+#endif // defined(XP_WIN) || defined(XP_LINUX)
+
+    cpuid_t cpuStart_;
+
+    // The performance group shared by this compartment and possibly
+    // others, or `nullptr` if another AutoStopwatch is already in
+    // charge of monitoring that group.
+    mozilla::RefPtr<js::PerformanceGroup> sharedGroup_;
+
+    // The toplevel group, representing the entire process, or `nullptr`
+    // if another AutoStopwatch is already in charge of monitoring that group.
+    mozilla::RefPtr<js::PerformanceGroup> topGroup_;
+
+    // The performance group specific to this compartment, or
+    // `nullptr` if another AutoStopwatch is already in charge of
+    // monitoring that group.
+    mozilla::RefPtr<js::PerformanceGroup> ownGroup_;
+
+ public:
     // If the stopwatch is active, constructing an instance of
     // AutoStopwatch causes it to become the current owner of the
     // stopwatch.
     //
     // Previous owner is restored upon destruction.
     explicit inline AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : cx_(cx)
       , iteration_(0)
       , isMonitoringJank_(false)
       , isMonitoringCPOW_(false)
-      , userTimeStart_(0)
-      , systemTimeStart_(0)
+      , cyclesStart_(0)
       , CPOWTimeStart_(0)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 
         JSCompartment* compartment = cx_->compartment();
         if (compartment->scheduledForDestruction)
             return;
 
         JSRuntime* runtime = cx_->runtime();
-        iteration_ = runtime->stopwatch.iteration;
+        iteration_ = runtime->stopwatch.iteration();
 
         sharedGroup_ = acquireGroup(compartment->performanceMonitoring.getSharedGroup(cx));
         if (sharedGroup_)
             topGroup_ = acquireGroup(runtime->stopwatch.performance.getOwnGroup());
 
         if (runtime->stopwatch.isMonitoringPerCompartment())
             ownGroup_ = acquireGroup(compartment->performanceMonitoring.getOwnGroup());
 
         if (!sharedGroup_ && !ownGroup_) {
             // We are not in charge of monitoring anything.
             return;
         }
 
+        // Now that we are sure that JS code is being executed,
+        // initialize the stopwatch for this iteration, lazily.
+        runtime->stopwatch.start();
         enter();
     }
     ~AutoStopwatch()
     {
         if (!sharedGroup_ && !ownGroup_) {
             // We are not in charge of monitoring anything.
-            // (isMonitoringForTop_ implies isMonitoringForGroup_,
-            // so we do not need to check it)
             return;
         }
 
         JSCompartment* compartment = cx_->compartment();
         if (compartment->scheduledForDestruction)
             return;
 
         JSRuntime* runtime = cx_->runtime();
-        if (iteration_ != runtime->stopwatch.iteration) {
+        if (iteration_ != runtime->stopwatch.iteration()) {
             // We have entered a nested event loop at some point.
             // Any information we may have is obsolete.
             return;
         }
 
+        // Finish and commit measures
+        exit();
+
         releaseGroup(sharedGroup_);
         releaseGroup(topGroup_);
         releaseGroup(ownGroup_);
-
-        // Finish and commit measures
-        exit();
     }
    private:
     void enter() {
         JSRuntime* runtime = cx_->runtime();
 
         if (runtime->stopwatch.isMonitoringCPOW()) {
-            CPOWTimeStart_ = runtime->stopwatch.performance.getOwnGroup()->data.totalCPOWTime;
+            CPOWTimeStart_ = runtime->stopwatch.totalCPOWTime;
             isMonitoringCPOW_ = true;
         }
 
         if (runtime->stopwatch.isMonitoringJank()) {
-            if (this->getTimes(runtime, &userTimeStart_, &systemTimeStart_)) {
-                isMonitoringJank_ = true;
-            }
+            cyclesStart_ = this->getCycles();
+            cpuStart_ = this->getCPU();
+            isMonitoringJank_ = true;
         }
 
     }
 
     void exit() {
         JSRuntime* runtime = cx_->runtime();
 
-        uint64_t userTimeDelta = 0;
-        uint64_t systemTimeDelta = 0;
+        uint64_t cyclesDelta = 0;
         if (isMonitoringJank_ && runtime->stopwatch.isMonitoringJank()) {
             // We were monitoring jank when we entered and we still are.
-            uint64_t userTimeEnd, systemTimeEnd;
-            if (!this->getTimes(runtime, &userTimeEnd, &systemTimeEnd)) {
-                // We make no attempt to recover from this error. If
-                // we bail out here, we lose nothing of value, plus
-                // I'm nearly sure that this error cannot happen in
-                // practice.
-                return;
+
+            // If possible, discard results when we don't end on the
+            // same CPU as we started.  Note that we can be
+            // rescheduled to another CPU beween `getCycles()` and
+            // `getCPU()`.  We hope that this will happen rarely
+            // enough that the impact on our statistics will remain
+            // limited.
+            const cpuid_t cpuEnd = this->getCPU();
+            if (isSameCPU(cpuStart_, cpuEnd)) {
+                const uint64_t cyclesEnd = getCycles();
+                cyclesDelta = getDelta(cyclesEnd, cyclesStart_);
             }
-            userTimeDelta = userTimeEnd - userTimeStart_;
-            systemTimeDelta = systemTimeEnd - systemTimeStart_;
+#if (defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA) || defined(XP_LINUX)
+            if (isSameCPU(cpuStart_, cpuEnd))
+                runtime->stopwatch.testCpuRescheduling.stayed += 1;
+            else
+                runtime->stopwatch.testCpuRescheduling.moved += 1;
+#endif // defined(XP_WIN) || defined(XP_LINUX)
         }
 
         uint64_t CPOWTimeDelta = 0;
         if (isMonitoringCPOW_ && runtime->stopwatch.isMonitoringCPOW()) {
             // We were monitoring CPOW when we entered and we still are.
-            CPOWTimeDelta = runtime->stopwatch.performance.getOwnGroup()->data.totalCPOWTime - CPOWTimeStart_;
+            const uint64_t CPOWTimeEnd = runtime->stopwatch.totalCPOWTime;
+            CPOWTimeDelta = getDelta(CPOWTimeEnd, CPOWTimeStart_);
 
         }
-        commitDeltasToGroups(userTimeDelta, systemTimeDelta, CPOWTimeDelta);
+        addToGroups(cyclesDelta, CPOWTimeDelta);
     }
 
     // Attempt to acquire a group
     // If the group is `null` or if the group already has a stopwatch,
     // do nothing and return `null`.
     // Otherwise, bind the group to `this` for the current iteration
     // and return `group`.
     PerformanceGroup* acquireGroup(PerformanceGroup* group) {
@@ -542,150 +568,114 @@ class AutoStopwatch final
     // Release a group.
     // Noop if `group` is null or if `this` is not the stopwatch
     // of `group` for the current iteration.
     void releaseGroup(PerformanceGroup* group) {
         if (group)
             group->releaseStopwatch(iteration_, this);
     }
 
-    void commitDeltasToGroups(uint64_t userTimeDelta, uint64_t systemTimeDelta,
-                              uint64_t CPOWTimeDelta) const {
-        applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, sharedGroup_);
-        applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, topGroup_);
-        applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, ownGroup_);
+    // Add recent changes to all the groups owned by this stopwatch.
+    // Mark the groups as changed recently.
+    void addToGroups(uint64_t cyclesDelta, uint64_t CPOWTimeDelta) {
+        addToGroup(cyclesDelta, CPOWTimeDelta, sharedGroup_);
+        addToGroup(cyclesDelta, CPOWTimeDelta, topGroup_);
+        addToGroup(cyclesDelta, CPOWTimeDelta, ownGroup_);
     }
 
-    void applyDeltas(uint64_t userTimeDelta, uint64_t systemTimeDelta,
-                     uint64_t CPOWTimeDelta, PerformanceGroup* group) const {
+    // Add recent changes to a single group. Mark the group as changed recently.
+    void addToGroup(uint64_t cyclesDelta, uint64_t CPOWTimeDelta, PerformanceGroup* group) {
         if (!group)
             return;
 
-        group->data.ticks++;
-
-        uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta;
-        group->data.totalUserTime += userTimeDelta;
-        group->data.totalSystemTime += systemTimeDelta;
-        group->data.totalCPOWTime += CPOWTimeDelta;
-
-        // Update an array containing the number of times we have missed
-        // at least 2^0 successive ms, 2^1 successive ms, ...
-        // 2^i successive ms.
-
-        // Duration of one frame, i.e. 16ms in museconds
-        size_t i = 0;
-        uint64_t duration = 1000;
-        for (i = 0, duration = 1000;
-             i < ArrayLength(group->data.durations) && duration < totalTimeDelta;
-             ++i, duration *= 2)
-        {
-            group->data.durations[i]++;
+        MOZ_ASSERT(group->hasStopwatch(iteration_, this));
+
+        if (group->recentTicks == 0) {
+            // First time we meet this group during the tick,
+            // mark it as needing updates.
+            JSRuntime* runtime = cx_->runtime();
+            runtime->stopwatch.addChangedGroup(group);
         }
+        group->recentTicks++;
+        group->recentCycles += cyclesDelta;
+        group->recentCPOW += CPOWTimeDelta;
+    }
+
+    // Perform a subtraction for a quantity that should be monotonic
+    // but is not guaranteed to be so.
+    //
+    // If `start <= end`, return `end - start`.
+    // Otherwise, return `0`.
+    uint64_t getDelta(const uint64_t end, const uint64_t start) const
+    {
+        if (start >= end)
+            return 0;
+        return end - start;
     }
 
-    // Get the OS-reported time spent in userland/systemland, in
-    // microseconds. On most platforms, this data is per-thread,
-    // but on some platforms we need to fall back to per-process.
-    bool getTimes(JSRuntime* runtime, uint64_t* userTime, uint64_t* systemTime) const {
-        MOZ_ASSERT(userTime);
-        MOZ_ASSERT(systemTime);
-
-#if defined(XP_MACOSX)
-        // On MacOS X, to get we per-thread data, we need to
-        // reach into the kernel.
-
-        mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
-        thread_basic_info_data_t info;
-        mach_port_t port = mach_thread_self();
-        kern_return_t err =
-            thread_info(/* [in] targeted thread*/ port,
-                        /* [in] nature of information*/ THREAD_BASIC_INFO,
-                        /* [out] thread information */  (thread_info_t)&info,
-                        /* [inout] number of items */   &count);
-
-        // We do not need ability to communicate with the thread, so
-        // let's release the port.
-        mach_port_deallocate(mach_task_self(), port);
-
-        if (err != KERN_SUCCESS)
-            return false;
-
-        *userTime = info.user_time.microseconds + info.user_time.seconds * 1000000;
-        *systemTime = info.system_time.microseconds + info.system_time.seconds * 1000000;
-
-#elif defined(XP_UNIX)
-        struct rusage rusage;
-#if defined(RUSAGE_THREAD)
-        // Under Linux, we can obtain per-thread statistics
-        int err = getrusage(RUSAGE_THREAD, &rusage);
+    // Return the value of the Timestamp Counter, as provided by the CPU.
+    // 0 on platforms for which we do not have access to a Timestamp Counter.
+    uint64_t getCycles() const
+    {
+#if defined(MOZ_HAVE_RDTSC)
+        return ReadTimestampCounter();
 #else
-        // Under other Unices, we need to do with more noisy
-        // per-process statistics.
-        int err = getrusage(RUSAGE_SELF, &rusage);
-#endif // defined(RUSAGE_THREAD)
-
-        if (err)
-            return false;
-
-        *userTime = rusage.ru_utime.tv_usec + rusage.ru_utime.tv_sec * 1000000;
-        *systemTime = rusage.ru_stime.tv_usec + rusage.ru_stime.tv_sec * 1000000;
-
-#elif defined(XP_WIN)
-        // Under Windows, we can obtain per-thread statistics,
-        // although experience seems to suggest that they are
-        // not very good under Windows XP.
-        FILETIME creationFileTime; // Ignored
-        FILETIME exitFileTime; // Ignored
-        FILETIME kernelFileTime;
-        FILETIME userFileTime;
-        BOOL success = GetThreadTimes(GetCurrentThread(),
-                                      &creationFileTime, &exitFileTime,
-                                      &kernelFileTime, &userFileTime);
-
-        if (!success)
-            return false;
-
-        ULARGE_INTEGER kernelTimeInt;
-        ULARGE_INTEGER userTimeInt;
-        kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime;
-        kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime;
-        // Convert 100 ns to 1 us, make sure that the result is monotonic
-        *systemTime = runtime->stopwatch.systemTimeFix.monotonize(kernelTimeInt.QuadPart / 10);
-
-        userTimeInt.LowPart = userFileTime.dwLowDateTime;
-        userTimeInt.HighPart = userFileTime.dwHighDateTime;
-        // Convert 100 ns to 1 us, make sure that the result is monotonic
-        *userTime = runtime->stopwatch.userTimeFix.monotonize(userTimeInt.QuadPart / 10);
-
-#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
-
+        return 0;
+#endif // defined(MOZ_HAVE_RDTSC)
+    }
+
+
+    // Return the identifier of the current CPU, on platforms for which we have
+    // access to the current CPU.
+    cpuid_t inline getCPU() const
+    {
+#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA
+        PROCESSOR_NUMBER proc;
+        GetCurrentProcessorNumberEx(&proc);
+
+        cpuid_t result(proc.Group, proc.Number);
+        return result;
+#elif defined(XP_LINUX)
+        return sched_getcpu();
+#else
+        return {};
+#endif // defined(XP_WIN) || defined(XP_LINUX)
+    }
+
+    // Compare two CPU identifiers.
+    bool inline isSameCPU(const cpuid_t& a, const cpuid_t& b) const
+    {
+#if defined(XP_WIN)  && WINVER >= _WIN32_WINNT_VISTA
+        return a.group_ == b.group_ && a.number_ == b.number_;
+#elif defined(XP_LINUX)
+        return a == b;
+#else
         return true;
+#endif
     }
-
-
-private:
+ private:
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
 };
 
 } // namespace js
 
 // MSVC with PGO inlines a lot of functions in RunScript, resulting in large
 // stack frames and stack overflow issues, see bug 1167883. Turn off PGO to
 // avoid this.
 #ifdef _MSC_VER
 # pragma optimize("g", off)
 #endif
 bool
 js::RunScript(JSContext* cx, RunState& state)
 {
     JS_CHECK_RECURSION(cx, return false);
 
-#if defined(NIGHTLY_BUILD)
+#if defined(NIGHTLY_BUILD) && defined(MOZ_HAVE_RDTSC)
     js::AutoStopwatch stopwatch(cx);
-#endif // defined(NIGHTLY_BUILD)
+#endif // defined(NIGHTLY_BUILD) && defined(MOZ_HAVE_RDTSC)
 
     SPSEntryMarker marker(cx->runtime(), state.script());
 
     state.script()->ensureNonLazyCanonicalFunction(cx);
 
     if (jit::IsIonEnabled(cx)) {
         jit::MethodStatus status = jit::CanEnter(cx, state);
         if (status == jit::Method_Error)
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -7,16 +7,25 @@
 #include "vm/Runtime-inl.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/ThreadLocal.h"
 
+#if defined(XP_MACOSX)
+#include <mach/mach.h>
+#elif defined(XP_UNIX)
+#include <sys/resource.h>
+#elif defined(XP_WIN)
+#include <processthreadsapi.h>
+#include <windows.h>
+#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
+
 #include <locale.h>
 #include <string.h>
 
 #ifdef JS_CAN_CHECK_THREADSAFE_ACCESSES
 # include <sys/mman.h>
 #endif
 
 #include "jsatom.h"
@@ -867,23 +876,310 @@ JS::UpdateJSRuntimeProfilerSampleBufferG
 
 JS_FRIEND_API(bool)
 JS::IsProfilingEnabledForRuntime(JSRuntime* runtime)
 {
     MOZ_ASSERT(runtime);
     return runtime->spsProfiler.enabled();
 }
 
+JS_PUBLIC_API(void)
+js::FlushPerformanceMonitoring(JSRuntime* runtime)
+{
+    MOZ_ASSERT(runtime);
+    return runtime->stopwatch.commit();
+}
+JS_PUBLIC_API(void)
+js::ResetPerformanceMonitoring(JSRuntime* runtime)
+{
+    MOZ_ASSERT(runtime);
+    return runtime->stopwatch.reset();
+}
+
 void
-js::ResetStopwatches(JSRuntime* rt)
+JSRuntime::Stopwatch::reset()
+{
+    // All ongoing measures are dependent on the current iteration#.
+    // By incrementing it, we mark all data as stale. Stale data will
+    // be overwritten progressively during the execution.
+    ++iteration_;
+    touchedGroups.clear();
+}
+
+void
+JSRuntime::Stopwatch::start()
+{
+    if (!isMonitoringJank_) {
+        return;
+    }
+
+    if (iteration_ == startedAtIteration_) {
+        // The stopwatch is already started for this iteration.
+        return;
+    }
+
+    startedAtIteration_ = iteration_;
+    if (!getResources(&userTimeStart_, &systemTimeStart_))
+        return;
+}
+
+// Commit the data that has been collected during the iteration
+// into the actual `PerformanceData`.
+//
+// We use the proportion of cycles-spent-in-group over
+// cycles-spent-in-toplevel-group as an approximation to allocate
+// system (kernel) time and user (CPU) time to each group. Note
+// that cycles are not an exact measure:
+//
+// 1. if the computer has gone to sleep, the clock may be reset to 0;
+// 2. if the process is moved between CPUs/cores, it may end up on a CPU
+//    or core with an unsynchronized clock;
+// 3. the mapping between clock cycles and walltime varies with the current
+//    frequency of the CPU;
+// 4. other threads/processes using the same CPU will also increment
+//    the counter.
+//
+// ** Effect of 1. (computer going to sleep)
+//
+// We assume that this will happen very seldom. Since the final numbers
+// are bounded by the CPU time and Kernel time reported by `getresources`,
+// the effect will be contained to a single iteration of the event loop.
+//
+// ** Effect of 2. (moving between CPUs/cores)
+//
+// On platforms that support it, we only measure the number of cycles
+// if we start and end execution of a group on the same
+// CPU/core. While there is a small window (a few cycles) during which
+// the thread can be migrated without us noticing, we expect that this
+// will happen rarely enough that this won't affect the statistics
+// meaningfully.
+//
+// On other platforms, assuming that the probability of jumping
+// between CPUs/cores during a given (real) cycle is constant, and
+// that the distribution of differences between clocks is even, the
+// probability that the number of cycles reported by a measure is
+// modified by X cycles should be a gaussian distribution, with groups
+// with longer execution having a larger amplitude than groups with
+// shorter execution. Since we discard measures that result in a
+// negative number of cycles, this distribution is actually skewed
+// towards over-estimating the number of cycles of groups that already
+// have many cycles and under-estimating the number of cycles that
+// already have fewer cycles.
+//
+// Since the final numbers are bounded by the CPU time and Kernel time
+// reported by `getresources`, we accept this bias.
+//
+// ** Effect of 3. (mapping between clock cycles and walltime)
+//
+// Assuming that this is evenly distributed, we expect that this will
+// eventually balance out.
+//
+// ** Effect of 4. (cycles increase with system activity)
+//
+// Assuming that, within an iteration of the event loop, this happens
+// unformly over time, this will skew towards over-estimating the number
+// of cycles of groups that already have many cycles and under-estimating
+// the number of cycles that already have fewer cycles.
+//
+// Since the final numbers are bounded by the CPU time and Kernel time
+// reported by `getresources`, we accept this bias.
+//
+// ** Big picture
+//
+// Computing the number of cycles is fast and should be accurate
+// enough in practice. Alternatives (such as calling `getresources`
+// all the time or sampling from another thread) are very expensive
+// in system calls and/or battery and not necessarily more accurate.
+void
+JSRuntime::Stopwatch::commit()
 {
-    MOZ_ASSERT(rt);
-    rt->stopwatch.reset();
+#if !defined(MOZ_HAVE_RDTSC)
+    // The AutoStopwatch is only executed if `MOZ_HAVE_RDTSC`.
+    return;
+#endif // !defined(MOZ_HAVE_RDTSC)
+
+    if (!isMonitoringJank_) {
+        // Either we have not started monitoring or monitoring has
+        // been cancelled during the iteration.
+        return;
+    }
+
+    if (startedAtIteration_ != iteration_) {
+        // No JS code has been monitored during this iteration.
+        return;
+    }
+
+    uint64_t userTimeStop, systemTimeStop;
+    if (!getResources(&userTimeStop, &systemTimeStop))
+        return;
+
+    // `getResources` is not guaranteed to be monotonic, so round up
+    // any negative result to 0 milliseconds.
+    uint64_t userTimeDelta = 0;
+    if (userTimeStop > userTimeStart_)
+        userTimeDelta = userTimeStop - userTimeStart_;
+
+    uint64_t systemTimeDelta = 0;
+    if (systemTimeStop > systemTimeStart_)
+        systemTimeDelta = systemTimeStop - systemTimeStart_;
+
+    mozilla::RefPtr<js::PerformanceGroup> group = performance.getOwnGroup();
+    const uint64_t totalRecentCycles = group->recentCycles;
+
+    mozilla::Vector<mozilla::RefPtr<js::PerformanceGroup>> recentGroups;
+    touchedGroups.swap(recentGroups);
+    MOZ_ASSERT(recentGroups.length() > 0);
+
+    // We should only reach this stage if `group` has had some activity.
+    MOZ_ASSERT(group->recentTicks > 0);
+    for (mozilla::RefPtr<js::PerformanceGroup>* iter = recentGroups.begin(); iter != recentGroups.end(); ++iter) {
+        transferDeltas(userTimeDelta, systemTimeDelta, totalRecentCycles, *iter);
+    }
+
+    // Make sure that `group` was treated along with the other items of `recentGroups`.
+    MOZ_ASSERT(group->recentTicks == 0);
+
+    // Finally, reset immediately, to make sure that we're not hit by the
+    // end of a nested event loop (which would cause `commit` to be called
+    // twice in succession).
+    reset();
 }
 
+void
+JSRuntime::Stopwatch::transferDeltas(uint64_t totalUserTimeDelta, uint64_t totalSystemTimeDelta,
+                                     uint64_t totalCyclesDelta, js::PerformanceGroup* group) {
+
+    const uint64_t ticksDelta = group->recentTicks;
+    const uint64_t cpowTimeDelta = group->recentCPOW;
+    const uint64_t cyclesDelta = group->recentCycles;
+    group->resetRecentData();
+
+    // We have now performed all cleanup and may `return` at any time without fear of leaks.
+
+    if (group->iteration() != iteration_) {
+        // Stale data, don't commit.
+        return;
+    }
+
+    // When we add a group as changed, we immediately set its
+    // `recentTicks` from 0 to 1.  If we have `ticksDelta == 0` at
+    // this stage, we have already called `resetRecentData` but we
+    // haven't removed it from the list.
+    MOZ_ASSERT(ticksDelta != 0);
+    MOZ_ASSERT(cyclesDelta <= totalCyclesDelta);
+    if (cyclesDelta == 0 || totalCyclesDelta == 0) {
+        // Nothing useful, don't commit.
+        return;
+    }
+
+    double proportion = (double)cyclesDelta / (double)totalCyclesDelta;
+    MOZ_ASSERT(proportion <= 1);
+
+    const uint64_t userTimeDelta = proportion * totalUserTimeDelta;
+    const uint64_t systemTimeDelta = proportion * totalSystemTimeDelta;
+
+    group->data.totalUserTime += userTimeDelta;
+    group->data.totalSystemTime += systemTimeDelta;
+    group->data.totalCPOWTime += cpowTimeDelta;
+    group->data.ticks += ticksDelta;
+
+    const uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta;
+
+    size_t i = 0;
+    uint64_t duration = 1000; // 1ms in ┬Ás
+    for (i = 0, duration = 1000;
+         i < mozilla::ArrayLength(group->data.durations) && duration < totalTimeDelta;
+         ++i, duration *= 2) {
+        group->data.durations[i]++;
+    }
+}
+
+// Get the OS-reported time spent in userland/systemland, in
+// microseconds. On most platforms, this data is per-thread,
+// but on some platforms we need to fall back to per-process.
+// Data is not guaranteed to be monotonic.
+bool
+JSRuntime::Stopwatch::getResources(uint64_t* userTime,
+                                   uint64_t* systemTime) const {
+    MOZ_ASSERT(userTime);
+    MOZ_ASSERT(systemTime);
+
+#if defined(XP_MACOSX)
+    // On MacOS X, to get we per-thread data, we need to
+    // reach into the kernel.
+
+    mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
+    thread_basic_info_data_t info;
+    mach_port_t port = mach_thread_self();
+    kern_return_t err =
+        thread_info(/* [in] targeted thread*/ port,
+                    /* [in] nature of information*/ THREAD_BASIC_INFO,
+                    /* [out] thread information */  (thread_info_t)&info,
+                    /* [inout] number of items */   &count);
+
+    // We do not need ability to communicate with the thread, so
+    // let's release the port.
+    mach_port_deallocate(mach_task_self(), port);
+
+    if (err != KERN_SUCCESS)
+        return false;
+
+    *userTime = info.user_time.microseconds + info.user_time.seconds * 1000000;
+    *systemTime = info.system_time.microseconds + info.system_time.seconds * 1000000;
+
+#elif defined(XP_UNIX)
+    struct rusage rusage;
+#if defined(RUSAGE_THREAD)
+    // Under Linux, we can obtain per-thread statistics
+    int err = getrusage(RUSAGE_THREAD, &rusage);
+#else
+    // Under other Unices, we need to do with more noisy
+    // per-process statistics.
+    int err = getrusage(RUSAGE_SELF, &rusage);
+#endif // defined(RUSAGE_THREAD)
+
+    if (err)
+        return false;
+
+    *userTime = rusage.ru_utime.tv_usec + rusage.ru_utime.tv_sec * 1000000;
+    *systemTime = rusage.ru_stime.tv_usec + rusage.ru_stime.tv_sec * 1000000;
+
+#elif defined(XP_WIN)
+    // Under Windows, we can obtain per-thread statistics,
+    // although experience seems to suggest that they are
+    // not very good under Windows XP.
+    FILETIME creationFileTime; // Ignored
+    FILETIME exitFileTime; // Ignored
+    FILETIME kernelFileTime;
+    FILETIME userFileTime;
+    BOOL success = GetThreadTimes(GetCurrentThread(),
+                                  &creationFileTime, &exitFileTime,
+                                  &kernelFileTime, &userFileTime);
+
+    if (!success)
+        return false;
+
+    ULARGE_INTEGER kernelTimeInt;
+    kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime;
+    kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime;
+    // Convert 100 ns to 1 us.
+    *systemTime = kernelTimeInt.QuadPart / 10;
+
+    ULARGE_INTEGER userTimeInt;
+    userTimeInt.LowPart = userFileTime.dwLowDateTime;
+    userTimeInt.HighPart = userFileTime.dwHighDateTime;
+    // Convert 100 ns to 1 us.
+    *userTime = userTimeInt.QuadPart / 10;
+
+#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
+
+    return true;
+}
+
+
 bool
 js::SetStopwatchIsMonitoringJank(JSRuntime* rt, bool value)
 {
     return rt->stopwatch.setIsMonitoringJank(value);
 }
 bool
 js::GetStopwatchIsMonitoringJank(JSRuntime* rt)
 {
@@ -907,16 +1203,23 @@ js::SetStopwatchIsMonitoringPerCompartme
     return rt->stopwatch.setIsMonitoringPerCompartment(value);
 }
 bool
 js::GetStopwatchIsMonitoringPerCompartment(JSRuntime* rt)
 {
     return rt->stopwatch.isMonitoringPerCompartment();
 }
 
+void
+js::GetPerfMonitoringTestCpuRescheduling(JSRuntime* rt, uint64_t* stayed, uint64_t* moved)
+{
+    *stayed = rt->stopwatch.testCpuRescheduling.stayed;
+    *moved = rt->stopwatch.testCpuRescheduling.moved;
+}
+
 js::PerformanceGroupHolder::~PerformanceGroupHolder()
 {
     unlink();
 }
 
 void*
 js::PerformanceGroupHolder::getHashKey(JSContext* cx)
 {
@@ -956,49 +1259,56 @@ js::PerformanceGroupHolder::getSharedGro
     void* key = getHashKey(cx);
     JSRuntime::Stopwatch::Groups::AddPtr ptr = runtime_->stopwatch.groups().lookupForAdd(key);
     if (ptr) {
         sharedGroup_ = ptr->value();
         MOZ_ASSERT(sharedGroup_);
     } else {
         sharedGroup_ = runtime_->new_<PerformanceGroup>(cx, key);
         if (!sharedGroup_)
-            return nullptr;
-
+          return nullptr;
         runtime_->stopwatch.groups().add(ptr, key, sharedGroup_);
     }
 
     return sharedGroup_;
 }
 
-PerformanceData*
-js::GetPerformanceData(JSRuntime* rt)
+void
+js::AddCPOWPerformanceDelta(JSRuntime* rt, uint64_t delta)
 {
-    return &rt->stopwatch.performance.getOwnGroup()->data;
+    rt->stopwatch.totalCPOWTime += delta;
 }
 
 js::PerformanceGroup::PerformanceGroup(JSRuntime* rt)
   : uid(rt->stopwatch.uniqueId()),
+    recentCycles(0),
+    recentTicks(0),
+    recentCPOW(0),
     runtime_(rt),
     stopwatch_(nullptr),
     iteration_(0),
     key_(nullptr),
     refCount_(0),
     isSharedGroup_(false)
-{ }
+{
+}
 
- js::PerformanceGroup::PerformanceGroup(JSContext* cx, void* key)
-   : uid(cx->runtime()->stopwatch.uniqueId()),
-     runtime_(cx->runtime()),
-     stopwatch_(nullptr),
-     iteration_(0),
-     key_(key),
-     refCount_(0),
-     isSharedGroup_(true)
-{ }
+js::PerformanceGroup::PerformanceGroup(JSContext* cx, void* key)
+  : uid(cx->runtime()->stopwatch.uniqueId()),
+    recentCycles(0),
+    recentTicks(0),
+    recentCPOW(0),
+    runtime_(cx->runtime()),
+    stopwatch_(nullptr),
+    iteration_(0),
+    key_(key),
+    refCount_(0),
+    isSharedGroup_(true)
+{
+}
 
 void
 js::PerformanceGroup::AddRef()
 {
     ++refCount_;
 }
 
 void
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -10,16 +10,17 @@
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
 
 #include <setjmp.h>
 
 #include "jsatom.h"
 #include "jsclist.h"
 #include "jsscript.h"
 
 #ifdef XP_MACOSX
@@ -1514,63 +1515,77 @@ struct JSRuntime : public JS::shadow::Ru
         }
 
         /**
          * Performance data on the entire runtime.
          */
         js::PerformanceGroupHolder performance;
 
         /**
-         * The number of times we have entered the event loop.
-         * Used to reset counters whenever we enter the loop,
-         * which may be caused either by having completed the
-         * previous run of the event loop, or by entering a
-         * nested loop.
-         *
-         * Always incremented by 1, may safely overflow.
-         */
-        uint64_t iteration;
-
-        /**
          * Callback used to ask the embedding to determine in which
          * Performance Group the current execution belongs. Typically, this is
          * used to regroup JSCompartments from several iframes from the same
          * page or from several compartments of the same addon into a single
          * Performance Group.
          *
          * May be `nullptr`, in which case we put all the JSCompartments
          * in the same PerformanceGroup.
          */
         JSCurrentPerfGroupCallback currentPerfGroupCallback;
 
+        /**
+         * The number of the current iteration of the event loop.
+         */
+        uint64_t iteration() {
+            return iteration_;
+        }
+
         explicit Stopwatch(JSRuntime* runtime)
           : performance(runtime)
-          , iteration(0)
           , currentPerfGroupCallback(nullptr)
+          , totalCPOWTime(0)
           , isMonitoringJank_(false)
           , isMonitoringCPOW_(false)
           , isMonitoringPerCompartment_(false)
+          , iteration_(0)
+          , startedAtIteration_(0)
           , idCounter_(0)
         { }
 
         /**
          * Reset the stopwatch.
          *
-         * This method is meant to be called whenever we start processing
-         * an event, to ensure that stop any ongoing measurement that would
-         * otherwise provide irrelevant results.
+         * This method is meant to be called whenever we start
+         * processing an event, to ensure that we stop any ongoing
+         * measurement that would otherwise provide irrelevant
+         * results.
          */
-        void reset() {
-            ++iteration;
-        }
+        void reset();
+
+        /**
+         * Start the stopwatch.
+         *
+         * This method is meant to be called once we know that the
+         * current event contains JavaScript code to execute. Calling
+         * this several times during the same iteration is idempotent.
+         */
+        void start();
+
+        /**
+         * Commit the performance data collected since the last call
+         * to `start()`, unless `reset()` has been called since then.
+         */
+        void commit();
+
         /**
          * Activate/deactivate stopwatch measurement of jank.
          *
-         * Noop if `value` is `true` and the stopwatch is already active,
-         * or if `value` is `false` and the stopwatch is already inactive.
+         * Noop if `value` is `true` and the stopwatch is already
+         * measuring jank, or if `value` is `false` and the stopwatch
+         * is not measuring jank.
          *
          * Otherwise, any pending measurements are dropped, but previous
          * measurements remain stored.
          *
          * May return `false` if the underlying hashtable cannot be allocated.
          */
         bool setIsMonitoringJank(bool value) {
             if (isMonitoringJank_ != value)
@@ -1583,16 +1598,28 @@ struct JSRuntime : public JS::shadow::Ru
 
             isMonitoringJank_ = value;
             return true;
         }
         bool isMonitoringJank() const {
             return isMonitoringJank_;
         }
 
+        /**
+         * Activate/deactivate stopwatch measurement per compartment.
+         *
+         * Noop if `value` is `true` and the stopwatch is already
+         * measuring per compartment, or if `value` is `false` and the
+         * stopwatch is not measuring per compartment.
+         *
+         * Otherwise, any pending measurements are dropped, but previous
+         * measurements remain stored.
+         *
+         * May return `false` if the underlying hashtable cannot be allocated.
+         */
         bool setIsMonitoringPerCompartment(bool value) {
             if (isMonitoringPerCompartment_ != value)
                 reset();
 
             if (value && !groups_.initialized()) {
                 if (!groups_.init(128))
                     return false;
             }
@@ -1601,73 +1628,163 @@ struct JSRuntime : public JS::shadow::Ru
             return true;
         }
         bool isMonitoringPerCompartment() const {
             return isMonitoringPerCompartment_;
         }
 
         /**
          * Activate/deactivate stopwatch measurement of CPOW.
+         *
+         * Noop if `value` is `true` and the stopwatch is already
+         * measuring CPOW, or if `value` is `false` and the stopwatch
+         * is not measuring CPOW.
+         *
+         * Otherwise, any pending measurements are dropped, but previous
+         * measurements remain stored.
+         *
+         * May return `false` if the underlying hashtable cannot be allocated.
          */
         bool setIsMonitoringCPOW(bool value) {
+            if (isMonitoringCPOW_ != value)
+                reset();
+
+            if (value && !groups_.initialized()) {
+                if (!groups_.init(128))
+                    return false;
+            }
+
             isMonitoringCPOW_ = value;
             return true;
         }
 
         bool isMonitoringCPOW() const {
             return isMonitoringCPOW_;
         }
 
         /**
          * Return a identifier for a group, unique to the runtime.
          */
         uint64_t uniqueId() {
             return idCounter_++;
         }
 
-        // Some systems have non-monotonic clocks. While we cannot
-        // improve the precision, we can make sure that our measures
-        // are monotonic nevertheless. We do this by storing the
-        // result of the latest call to the clock and making sure
-        // that the next timestamp is greater or equal.
-        struct MonotonicTimeStamp {
-            MonotonicTimeStamp()
-              : latestGood_(0)
-            {}
-            inline uint64_t monotonize(uint64_t stamp)
-            {
-                if (stamp <= latestGood_)
-                    return latestGood_;
-                latestGood_ = stamp;
-                return stamp;
-            }
-          private:
-            uint64_t latestGood_;
+        /**
+         * Mark a group as changed during the current iteration.
+         *
+         * Recent data from this group will be post-processed and
+         * committed at the end of the iteration.
+         */
+        void addChangedGroup(js::PerformanceGroup* group) {
+            MOZ_ASSERT(group->recentTicks == 0);
+            touchedGroups.append(group);
+        }
+
+        // The total amount of time spent waiting on CPOWs since the
+        // start of the process, in microseconds.
+        uint64_t totalCPOWTime;
+
+        // Data extracted by the AutoStopwatch to determine how often
+        // we reschedule the process to a different CPU during the
+        // execution of JS.
+        //
+        // Warning: These values are incremented *only* on platforms
+        // that offer a syscall/libcall to check on which CPU a
+        // process is currently executed.
+        struct TestCpuRescheduling
+        {
+            // Incremented once we have finished executing code
+            // in a group, if the CPU on which we started
+            // execution is the same as the CPU on which
+            // we finished.
+            uint64_t stayed;
+            // Incremented once we have finished executing code
+            // in a group, if the CPU on which we started
+            // execution is different from the CPU on which
+            // we finished.
+            uint64_t moved;
+            TestCpuRescheduling()
+              : stayed(0),
+                moved(0)
+            { }
         };
-        MonotonicTimeStamp systemTimeFix;
-        MonotonicTimeStamp userTimeFix;
+        TestCpuRescheduling testCpuRescheduling;
 
     private:
         Stopwatch(const Stopwatch&) = delete;
         Stopwatch& operator=(const Stopwatch&) = delete;
 
+        // Commit a piece of data to a single group.
+        // `totalUserTimeDelta`, `totalSystemTimeDelta`, `totalCyclesDelta`
+        // represent the outer measures, taken for the entire runtime.
+        void transferDeltas(uint64_t totalUserTimeDelta,
+                            uint64_t totalSystemTimeDelta,
+                            uint64_t totalCyclesDelta,
+                            js::PerformanceGroup* destination);
+
+        // Query the OS for the time spent in CPU/kernel since process
+        // launch.
+        bool getResources(uint64_t* userTime, uint64_t* systemTime) const;
+
+    private:
         Groups groups_;
         friend struct js::PerformanceGroupHolder;
 
         /**
-         * `true` if stopwatch monitoring is active, `false` otherwise.
+         * `true` if stopwatch monitoring is active for Jank, `false` otherwise.
          */
         bool isMonitoringJank_;
+        /**
+         * `true` if stopwatch monitoring is active for CPOW, `false` otherwise.
+         */
         bool isMonitoringCPOW_;
+        /**
+         * `true` if the stopwatch should udpdate data per-compartment, in
+         * addition to data per-group.
+         */
         bool isMonitoringPerCompartment_;
 
         /**
+         * The number of times we have entered the event loop.
+         * Used to reset counters whenever we enter the loop,
+         * which may be caused either by having completed the
+         * previous run of the event loop, or by entering a
+         * nested loop.
+         *
+         * Always incremented by 1, may safely overflow.
+         */
+        uint64_t iteration_;
+
+        /**
+         * The iteration at which the stopwatch was last started.
+         *
+         * Used both to avoid starting the stopwatch several times
+         * during the same event loop and to avoid committing stale
+         * stopwatch results.
+         */
+        uint64_t startedAtIteration_;
+
+        /**
          * A counter used to generate unique identifiers for groups.
          */
         uint64_t idCounter_;
+
+        /**
+         * The timestamps returned by `getResources()` during the call to
+         * `start()` in the current iteration of the event loop.
+         */
+        uint64_t userTimeStart_;
+        uint64_t systemTimeStart_;
+
+        /**
+         * Performance groups used during the current event.
+         *
+         * They are cleared by `commit()` and `reset()`.
+         */
+        mozilla::Vector<mozilla::RefPtr<js::PerformanceGroup>> touchedGroups;
     };
     Stopwatch stopwatch;
 };
 
 namespace js {
 
 // When entering JIT code, the calling JSContext* is stored into the thread's
 // PerThreadData. This function retrieves the JSContext with the pre-condition
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -30,17 +30,17 @@
 #include "mozilla/Services.h"
 #include "mozilla/dom/ScriptSettings.h"
 
 #include "nsContentUtils.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsCycleCollectionNoteRootCallback.h"
 #include "nsCycleCollector.h"
 #include "nsScriptLoader.h"
-#include "jsfriendapi.h"
+#include "jsapi.h"
 #include "jsprf.h"
 #include "js/MemoryMetrics.h"
 #include "mozilla/dom/GeneratedAtomList.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
@@ -3597,17 +3597,20 @@ XPCJSRuntime::BeforeProcessTask(bool aMi
 
             NS_DispatchToMainThread(new DummyRunnable());
         }
     }
 
     // Start the slow script timer.
     mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
     mSlowScriptSecondHalf = false;
-    js::ResetStopwatches(Get()->Runtime());
+
+    // As we may be entering a nested event loop, we need to
+    // cancel any ongoing performance measurement.
+    js::ResetPerformanceMonitoring(Get()->Runtime());
 
     // Push a null JSContext so that we don't see any script during
     // event processing.
     PushNullJSContext();
 
     CycleCollectedJSRuntime::BeforeProcessTask(aMightBlock);
 }
 
@@ -3619,16 +3622,20 @@ XPCJSRuntime::AfterProcessTask(uint32_t 
     mSlowScriptSecondHalf = false;
 
     // Call cycle collector occasionally.
     MOZ_ASSERT(NS_IsMainThread());
     nsJSContext::MaybePokeCC();
 
     CycleCollectedJSRuntime::AfterProcessTask(aNewRecursionDepth);
 
+    // Now that we are certain that the event is complete,
+    // we can flush any ongoing performance measurement.
+    js::FlushPerformanceMonitoring(Get()->Runtime());
+
     PopNullJSContext();
 }
 
 /***************************************************************************/
 
 void
 XPCJSRuntime::DebugDump(int16_t depth)
 {
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -4,16 +4,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/. */
 
 #include "nsLayoutUtils.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/EventDispatcher.h"
+#include "mozilla/FloatingPoint.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/Likely.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsCharTraits.h"
 #include "nsFontMetrics.h"
 #include "nsPresContext.h"
 #include "nsIContent.h"
@@ -448,16 +449,25 @@ GetSuitableScale(float aMaxScale, float 
                  nscoord aVisibleDimension, nscoord aDisplayDimension)
 {
   float displayVisibleRatio = float(aDisplayDimension) /
                               float(aVisibleDimension);
   // We want to rasterize based on the largest scale used during the
   // transform animation, unless that would make us rasterize something
   // larger than the screen.  But we never want to go smaller than the
   // minimum scale over the animation.
+  if (FuzzyEqualsMultiplicative(displayVisibleRatio, aMaxScale, .01f)) {
+    // Using aMaxScale may make us rasterize something a fraction larger than
+    // the screen. However, if aMaxScale happens to be the final scale of a
+    // transform animation it is better to use aMaxScale so that for the
+    // fraction of a second before we delayerize the composited texture it has
+    // a better chance of being pixel aligned and composited without resampling
+    // (avoiding visually clunky delayerization).
+    return aMaxScale;
+  }
   return std::max(std::min(aMaxScale, displayVisibleRatio), aMinScale);
 }
 
 static void
 GetMinAndMaxScaleForAnimationProperty(const nsIFrame* aFrame,
                                       AnimationCollection* aAnimations,
                                       gfxSize& aMaxScale,
                                       gfxSize& aMinScale)
new file mode 100644
--- /dev/null
+++ b/layout/forms/crashtests/1182414.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<head>
+<meta charset="UTF-8">
+<style type="text/css">
+#menu { position: fixed; left: 0px; top: 0px; }
+</style>
+</head>
+<body>
+	<svg id="canvas" width="2427" height="2295.5" version="1.1" xmlns="http://www.w3.org/2000/svg"></svg>
+
+<div id="menu">
+	<input id="chooseSize" type="range">
+</div>
+</body>
+</html>
+
--- a/layout/forms/crashtests/crashtests.list
+++ b/layout/forms/crashtests/crashtests.list
@@ -57,8 +57,10 @@ load 682684.xhtml
 load 865602.html
 load 944198.html
 load 949891.xhtml
 load 959311.html
 load 960277-2.html
 load 997709-1.html
 load 1102791.html
 load 1140216.html
+load 1182414.html
+
--- a/layout/forms/nsMeterFrame.cpp
+++ b/layout/forms/nsMeterFrame.cpp
@@ -260,25 +260,28 @@ nscoord
 nsMeterFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
 {
   return GetMinISize(aRenderingContext);
 }
 
 bool
 nsMeterFrame::ShouldUseNativeStyle() const
 {
+  nsIFrame* barFrame = mBarDiv->GetPrimaryFrame();
+
   // Use the native style if these conditions are satisfied:
   // - both frames use the native appearance;
   // - neither frame has author specified rules setting the border or the
   //   background.
   return StyleDisplay()->mAppearance == NS_THEME_METERBAR &&
-         mBarDiv->GetPrimaryFrame()->StyleDisplay()->mAppearance == NS_THEME_METERBAR_CHUNK &&
          !PresContext()->HasAuthorSpecifiedRules(this,
                                                  NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND) &&
-         !PresContext()->HasAuthorSpecifiedRules(mBarDiv->GetPrimaryFrame(),
+         barFrame &&
+         barFrame->StyleDisplay()->mAppearance == NS_THEME_METERBAR_CHUNK &&
+         !PresContext()->HasAuthorSpecifiedRules(barFrame,
                                                  NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND);
 }
 
 Element*
 nsMeterFrame::GetPseudoElement(nsCSSPseudoElements::Type aType)
 {
   if (aType == nsCSSPseudoElements::ePseudo_mozMeterBar) {
     return mBarDiv;
--- a/layout/forms/nsProgressFrame.cpp
+++ b/layout/forms/nsProgressFrame.cpp
@@ -266,25 +266,28 @@ nscoord
 nsProgressFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
 {
   return GetMinISize(aRenderingContext);
 }
 
 bool
 nsProgressFrame::ShouldUseNativeStyle() const
 {
+  nsIFrame* barFrame = mBarDiv->GetPrimaryFrame();
+
   // Use the native style if these conditions are satisfied:
   // - both frames use the native appearance;
   // - neither frame has author specified rules setting the border or the
   //   background.
   return StyleDisplay()->mAppearance == NS_THEME_PROGRESSBAR &&
-         mBarDiv->GetPrimaryFrame()->StyleDisplay()->mAppearance == NS_THEME_PROGRESSBAR_CHUNK &&
          !PresContext()->HasAuthorSpecifiedRules(this,
                                                  NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND) &&
-         !PresContext()->HasAuthorSpecifiedRules(mBarDiv->GetPrimaryFrame(),
+         barFrame &&
+         barFrame->StyleDisplay()->mAppearance == NS_THEME_PROGRESSBAR_CHUNK &&
+         !PresContext()->HasAuthorSpecifiedRules(barFrame,
                                                  NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND);
 }
 
 Element*
 nsProgressFrame::GetPseudoElement(nsCSSPseudoElements::Type aType)
 {
   if (aType == nsCSSPseudoElements::ePseudo_mozProgressBar) {
     return mBarDiv;
--- a/layout/forms/nsRangeFrame.cpp
+++ b/layout/forms/nsRangeFrame.cpp
@@ -832,25 +832,32 @@ nsRangeFrame::GetType() const
 #define STYLES_DISABLING_NATIVE_THEMING \
   NS_AUTHOR_SPECIFIED_BACKGROUND | \
   NS_AUTHOR_SPECIFIED_PADDING | \
   NS_AUTHOR_SPECIFIED_BORDER
 
 bool
 nsRangeFrame::ShouldUseNativeStyle() const
 {
+  nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
+  nsIFrame* progressFrame = mProgressDiv->GetPrimaryFrame();
+  nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
+
   return (StyleDisplay()->mAppearance == NS_THEME_RANGE) &&
          !PresContext()->HasAuthorSpecifiedRules(this,
                                                  (NS_AUTHOR_SPECIFIED_BORDER |
                                                   NS_AUTHOR_SPECIFIED_BACKGROUND)) &&
-         !PresContext()->HasAuthorSpecifiedRules(mTrackDiv->GetPrimaryFrame(),
+         trackFrame &&
+         !PresContext()->HasAuthorSpecifiedRules(trackFrame,
                                                  STYLES_DISABLING_NATIVE_THEMING) &&
-         !PresContext()->HasAuthorSpecifiedRules(mProgressDiv->GetPrimaryFrame(),
+         progressFrame &&
+         !PresContext()->HasAuthorSpecifiedRules(progressFrame,
                                                  STYLES_DISABLING_NATIVE_THEMING) &&
-         !PresContext()->HasAuthorSpecifiedRules(mThumbDiv->GetPrimaryFrame(),
+         thumbFrame &&
+         !PresContext()->HasAuthorSpecifiedRules(thumbFrame,
                                                  STYLES_DISABLING_NATIVE_THEMING);
 }
 
 Element*
 nsRangeFrame::GetPseudoElement(nsCSSPseudoElements::Type aType)
 {
   if (aType == nsCSSPseudoElements::ePseudo_mozRangeTrack) {
     return mTrackDiv;
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -552,19 +552,19 @@ fails-if(OSX) != 359903-1.html 359903-1-
 skip-if(Android) == 363706-1.html 363706-1-ref.html
 != 363706-1.html about:blank
 == 363728-1.html 363728-1-ref.html
 == 363728-2.html 363728-2-ref.html
 skip-if(B2G||Mulet) == 363858-1.html 363858-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == 363858-2.html 363858-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == 363858-3.html 363858-3-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == 363858-4.html 363858-4-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
-fuzzy-if(OSX>=1008,45,2) fuzzy-if(winWidget,37,1) == 363858-5a.html 363858-5-ref.html
+fuzzy-if(OSX>=1008,45,2) fuzzy-if(winWidget,114,1) == 363858-5a.html 363858-5-ref.html
 == 363858-5b.html 363858-5-ref.html
-fuzzy-if(OSX>=1008,45,2) fuzzy-if(winWidget,37,1) == 363858-6a.html 363858-6-ref.html
+fuzzy-if(OSX>=1008,45,2) fuzzy-if(winWidget,114,1) == 363858-6a.html 363858-6-ref.html
 == 363858-6b.html 363858-6-ref.html
 == 363874.html 363874-ref.html
 == 363874-max-width.html 363874-max-width-ref.html
 == 364066-1.html 364066-1-ref.html
 == 364079-1.html 364079-1-ref.html
 == 364318-1.xhtml 364318-1-ref.xhtml
 == 364861-1.html 364861-1-ref.html
 skip-if(B2G||Mulet) == 364862-1.html 364862-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
--- a/layout/reftests/forms/placeholder/reftest.list
+++ b/layout/reftests/forms/placeholder/reftest.list
@@ -11,17 +11,17 @@
 == placeholder-1-text.html placeholder-visible-ref.html
 == placeholder-1-password.html placeholder-visible-ref.html
 == placeholder-1-textarea.html placeholder-visible-textarea-ref.html
 == placeholder-2.html placeholder-visible-ref.html
 == placeholder-2-textarea.html placeholder-visible-textarea-ref.html
 == placeholder-3.html placeholder-overridden-ref.html
 == placeholder-4.html placeholder-overridden-ref.html
 == placeholder-5.html placeholder-visible-ref.html
-fuzzy-if(winWidget,160,7) fuzzy-if(asyncPan&&!layersGPUAccelerated,146,299) == placeholder-6.html placeholder-overflow-ref.html
+fuzzy-if(winWidget,160,10) fuzzy-if(asyncPan&&!layersGPUAccelerated,146,299) == placeholder-6.html placeholder-overflow-ref.html
 skip-if(B2G||Mulet) == placeholder-6-textarea.html placeholder-overflow-textarea-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 # needs-focus == placeholder-7.html placeholder-focus-ref.html
 # needs-focus == placeholder-8.html placeholder-focus-ref.html
 # needs-focus == placeholder-9.html placeholder-focus-ref.html
 needs-focus == placeholder-10.html placeholder-visible-ref.html
 == placeholder-11.html placeholder-visible-ref.html
 == placeholder-12.html placeholder-visible-ref.html
 == placeholder-13.html placeholder-visible-ref.html
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -450,24 +450,18 @@ nsAnimationManager::CheckAnimationRule(n
         // Update the old from the new so we can keep the original object
         // identity (and any expando properties attached to it).
         if (oldAnim->GetEffect() && newAnim->GetEffect()) {
           KeyframeEffectReadOnly* oldEffect = oldAnim->GetEffect();
           KeyframeEffectReadOnly* newEffect = newAnim->GetEffect();
           animationChanged =
             oldEffect->Timing() != newEffect->Timing() ||
             oldEffect->Properties() != newEffect->Properties();
-          oldEffect->Timing() = newEffect->Timing();
+          oldEffect->SetTiming(newEffect->Timing(), *oldAnim);
           oldEffect->Properties() = newEffect->Properties();
-          // FIXME: Currently assigning to KeyframeEffect::Timing() does not
-          // update the corresponding Animation (which may, for example, no
-          // longer be finished). Until we introduce proper setters for
-          // properties on effects, we need to manually cause the owning
-          // Animation to update its timing by setting the effect again.
-          oldAnim->SetEffect(oldEffect);
         }
 
         // Reset compositor state so animation will be re-synchronized.
         oldAnim->ClearIsRunningOnCompositor();
 
         // Handle changes in play state. If the animation is idle, however,
         // changes to animation-play-state should *not* restart it.
         if (oldAnim->PlayState() != AnimationPlayState::Idle) {
@@ -502,20 +496,16 @@ nsAnimationManager::CheckAnimationRule(n
         //
         // Although we're doing this while iterating this is safe because
         // we're not changing the length of newAnimations and we've finished
         // iterating over the list of old iterations.
         newAnim->CancelFromStyle();
         newAnim = nullptr;
         newAnimations.ReplaceElementAt(newIdx, oldAnim);
         collection->mAnimations.RemoveElementAt(oldIdx);
-
-        // We've touched the old animation's timing properties, so this
-        // could update the old animation's relevance.
-        oldAnim->UpdateRelevance();
       }
     }
   } else {
     collection =
       GetAnimations(aElement, aStyleContext->GetPseudoType(), true);
     for (Animation* animation : newAnimations) {
       // FIXME: Bug 1134163 - As above, we have shouldn't actually need to
       // queue events here. (But we do for now since some tests expect
--- a/mfbt/nsRefPtr.h
+++ b/mfbt/nsRefPtr.h
@@ -544,44 +544,52 @@ operator!=(const nsRefPtr<T>& aLhs, U* a
 
 template <class T, class U>
 inline bool
 operator!=(U* aLhs, const nsRefPtr<T>& aRhs)
 {
   return const_cast<const U*>(aLhs) != static_cast<const T*>(aRhs.get());
 }
 
-// Comparing an |nsRefPtr| to |nullptr|
+namespace detail {
+class nsRefPtrZero;
+} // namespace detail
+
+// Comparing an |nsRefPtr| to |0|
 
 template <class T>
 inline bool
-operator==(const nsRefPtr<T>& aLhs, decltype(nullptr))
+operator==(const nsRefPtr<T>& aLhs, ::detail::nsRefPtrZero* aRhs)
+// specifically to allow |smartPtr == 0|
 {
-  return aLhs.get() == nullptr;
+  return static_cast<const void*>(aLhs.get()) == reinterpret_cast<const void*>(aRhs);
 }
 
 template <class T>
 inline bool
-operator==(decltype(nullptr), const nsRefPtr<T>& aRhs)
+operator==(::detail::nsRefPtrZero* aLhs, const nsRefPtr<T>& aRhs)
+// specifically to allow |0 == smartPtr|
 {
-  return nullptr == aRhs.get();
+  return reinterpret_cast<const void*>(aLhs) == static_cast<const void*>(aRhs.get());
 }
 
 template <class T>
 inline bool
-operator!=(const nsRefPtr<T>& aLhs, decltype(nullptr))
+operator!=(const nsRefPtr<T>& aLhs, ::detail::nsRefPtrZero* aRhs)
+// specifically to allow |smartPtr != 0|
 {
-  return aLhs.get() != nullptr;
+  return static_cast<const void*>(aLhs.get()) != reinterpret_cast<const void*>(aRhs);
 }
 
 template <class T>
 inline bool
-operator!=(decltype(nullptr), const nsRefPtr<T>& aRhs)
+operator!=(::detail::nsRefPtrZero* aLhs, const nsRefPtr<T>& aRhs)
+// specifically to allow |0 != smartPtr|
 {
-  return nullptr != aRhs.get();
+  return reinterpret_cast<const void*>(aLhs) != static_cast<const void*>(aRhs.get());
 }
 
 /*****************************************************************************/
 
 template <class T>
 inline already_AddRefed<T>
 do_AddRef(T*&& aObj)
 {
--- a/nsprpub/TAG-INFO
+++ b/nsprpub/TAG-INFO
@@ -1,1 +1,1 @@
-NSPR_4_10_9_BETA3
+NSPR_4_10_9_RTM
--- a/nsprpub/config/prdepend.h
+++ b/nsprpub/config/prdepend.h
@@ -5,8 +5,9 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSPR in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
+
--- a/nsprpub/pr/include/prinit.h
+++ b/nsprpub/pr/include/prinit.h
@@ -26,21 +26,21 @@ PR_BEGIN_EXTERN_C
 /*
 ** NSPR's version is used to determine the likelihood that the version you
 ** used to build your component is anywhere close to being compatible with
 ** what is in the underlying library.
 **
 ** The format of the version string is
 **     "<major version>.<minor version>[.<patch level>] [<Beta>]"
 */
-#define PR_VERSION  "4.10.9 Beta"
+#define PR_VERSION  "4.10.9"
 #define PR_VMAJOR   4
 #define PR_VMINOR   10
 #define PR_VPATCH   9
-#define PR_BETA     PR_TRUE
+#define PR_BETA     PR_FALSE
 
 /*
 ** PRVersionCheck
 **
 ** The basic signature of the function that is called to provide version
 ** checking. The result will be a boolean that indicates the likelihood
 ** that the underling library will perform as the caller expects.
 **
--- a/python/mozbuild/mozbuild/controller/building.py
+++ b/python/mozbuild/mozbuild/controller/building.py
@@ -62,50 +62,44 @@ class TierStatus(object):
 
     The build system is organized into linear phases called tiers. Each tier
     executes in the order it was defined, 1 at a time.
     """
 
     def __init__(self, resources):
         """Accepts a SystemResourceMonitor to record results against."""
         self.tiers = OrderedDict()
-        self.active_tiers = set()
+        self.tier_status = OrderedDict()
         self.resources = resources
 
     def set_tiers(self, tiers):
         """Record the set of known tiers."""
         for tier in tiers:
             self.tiers[tier] = dict(
                 begin_time=None,
                 finish_time=None,
                 duration=None,
             )
+            self.tier_status[tier] = None
 
     def begin_tier(self, tier):
         """Record that execution of a tier has begun."""
+        self.tier_status[tier] = 'active'
         t = self.tiers[tier]
         # We should ideally use a monotonic clock here. Unfortunately, we won't
         # have one until Python 3.
         t['begin_time'] = time.time()
         self.resources.begin_phase(tier)
-        self.active_tiers.add(tier)
 
     def finish_tier(self, tier):
         """Record that execution of a tier has finished."""
+        self.tier_status[tier] = 'finished'
         t = self.tiers[tier]
         t['finish_time'] = time.time()
         t['duration'] = self.resources.finish_phase(tier)
-        self.active_tiers.remove(tier)
-
-    def tier_status(self):
-        for tier, state in self.tiers.items():
-            active = tier in self.active_tiers
-            finished = state['finish_time'] is not None
-
-            yield tier, active, finished
 
     def tiered_resource_usage(self):
         """Obtains an object containing resource usage for tiers.
 
         The returned object is suitable for serialization.
         """
         o = []
 
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -1,15 +1,16 @@
 # 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/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import argparse
+import itertools
 import json
 import logging
 import operator
 import os
 import subprocess
 import sys
 
 import mozpack.path as mozpath
@@ -120,83 +121,74 @@ class BuildProgressFooter(object):
     progress information collected from a BuildMonitor. This class converts the
     state of BuildMonitor into terminal output.
     """
 
     def __init__(self, terminal, monitor):
         # terminal is a blessings.Terminal.
         self._t = terminal
         self._fh = sys.stdout
-        self._monitor = monitor
-
-    def _clear_lines(self, n):
-        self._fh.write(self._t.move_x(0))
-        self._fh.write(self._t.clear_eos())
+        self.tiers = monitor.tiers.tier_status.viewitems()
 
     def clear(self):
         """Removes the footer from the current terminal."""
-        self._clear_lines(1)
+        self._fh.write(self._t.move_x(0))
+        self._fh.write(self._t.clear_eos())
 
     def draw(self):
         """Draws this footer in the terminal."""
-        tiers = self._monitor.tiers
 
-        if not tiers.tiers:
+        if not self.tiers:
             return
 
         # The drawn terminal looks something like:
         # TIER: base nspr nss js platform app SUBTIER: static export libs tools DIRECTORIES: 06/09 (memory)
 
         # This is a list of 2-tuples of (encoding function, input). None means
         # no encoding. For a full reason on why we do things this way, read the
         # big comment below.
-        parts = [('bold', 'TIER'), ':', ' ']
-
-        for tier, active, finished in tiers.tier_status():
-            if active:
-                parts.extend([('underline_yellow', tier), ' '])
-            elif finished:
-                parts.extend([('green', tier), ' '])
+        parts = [('bold', 'TIER:')]
+        append = parts.append
+        for tier, status in self.tiers:
+            if status is None:
+                append(tier)
+            elif status == 'finished':
+                append(('green', tier))
             else:
-                parts.extend([tier, ' '])
+                append(('underline_yellow', tier))
 
         # We don't want to write more characters than the current width of the
         # terminal otherwise wrapping may result in weird behavior. We can't
         # simply truncate the line at terminal width characters because a)
         # non-viewable escape characters count towards the limit and b) we
         # don't want to truncate in the middle of an escape sequence because
         # subsequent output would inherit the escape sequence.
         max_width = self._t.width
         written = 0
         write_pieces = []
         for part in parts:
-            if isinstance(part, tuple):
-                func, arg = part
-
-                if written + len(arg) > max_width:
-                    write_pieces.append(arg[0:max_width - written])
-                    written += len(arg)
-                    break
-
-                encoded = getattr(self._t, func)(arg)
+            try:
+                func, part = part
+                encoded = getattr(self._t, func)(part)
+            except ValueError:
+                encoded = part
 
-                write_pieces.append(encoded)
-                written += len(arg)
-            else:
-                if written + len(part) > max_width:
-                    write_pieces.append(part[0:max_width - written])
-                    written += len(part)
-                    break
+            len_part = len(part)
+            len_spaces = len(write_pieces)
+            if written + len_part + len_spaces > max_width:
+                write_pieces.append(part[0:max_width - written - len_spaces])
+                written += len_part
+                break
 
-                write_pieces.append(part)
-                written += len(part)
+            write_pieces.append(encoded)
+            written += len_part
+
         with self._t.location():
             self._t.move(self._t.height-1,0)
-            self._fh.write(''.join(write_pieces))
-        self._fh.flush()
+            self._fh.write(' '.join(write_pieces))
 
 
 class BuildOutputManager(LoggingMixin):
     """Handles writing build output to a terminal, to logs, etc."""
 
     def __init__(self, log_manager, monitor):
         self.populate_logger()
 
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -49,16 +49,17 @@ skip-if = toolkit == 'android' || toolki
 
 [test_cert_keyUsage.js]
 [test_logoutAndTeardown.js]
 run-sequentially = hardcoded ports
 [test_ocsp_stapling.js]
 run-sequentially = hardcoded ports
 [test_cert_blocklist.js]
 skip-if = buildapp == "b2g"
+tags = addons
 [test_ocsp_stapling_expired.js]
 run-sequentially = hardcoded ports
 skip-if = (toolkit == 'gonk' && debug) # Bug 1029775
 [test_ocsp_stapling_with_intermediate.js]
 run-sequentially = hardcoded ports
 [test_ocsp_caching.js]
 run-sequentially = hardcoded ports
 [test_ocsp_required.js]
@@ -73,16 +74,17 @@ run-sequentially = hardcoded ports
 run-sequentially = hardcoded ports
 [test_intermediate_basic_usage_constraints.js]
 [test_name_constraints.js]
 [test_cert_trust.js]
 [test_cert_version.js]
 [test_signed_apps.js]
 [test_signed_apps-marketplace.js]
 [test_signed_dir.js]
+tags = addons
 
 [test_cert_eku-CA_EP.js]
 [test_cert_eku-CA_EP_NS_OS_SA_TS.js]
 [test_cert_eku-CA.js]
 [test_cert_eku-CA_NS.js]
 [test_cert_eku-CA_OS.js]
 [test_cert_eku-CA_SA.js]
 [test_cert_eku-CA_TS.js]
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-NSS_3_20_RC0
+NSS_3_20_RTM
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -5,9 +5,8 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSS in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
-
--- a/services/healthreport/tests/xpcshell/xpcshell.ini
+++ b/services/healthreport/tests/xpcshell/xpcshell.ini
@@ -3,16 +3,17 @@ head = head.js
 tail =
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 
 [test_load_modules.js]
 [test_profile.js]
 [test_healthreporter.js]
 [test_provider_addons.js]
 skip-if = buildapp == 'mulet'
+tags = addons
 [test_provider_appinfo.js]
 [test_provider_crashes.js]
 skip-if = !crashreporter
 [test_provider_hotfix.js]
 [test_provider_places.js]
 [test_provider_searches.js]
 [test_provider_sysinfo.js]
 [test_provider_sessions.js]
--- a/services/sync/tests/unit/xpcshell.ini
+++ b/services/sync/tests/unit/xpcshell.ini
@@ -31,16 +31,17 @@ support-files =
 [test_utils_lock.js]
 [test_utils_makeGUID.js]
 [test_utils_notify.js]
 [test_utils_passphrase.js]
 
 # We have a number of other libraries that are pretty much standalone.
 [test_addon_utils.js]
 run-sequentially = Restarts server, can't change pref.
+tags = addons
 [test_httpd_sync_server.js]
 [test_jpakeclient.js]
 # Bug 618233: this test produces random failures on Windows 7.
 # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini)
 skip-if = os == "win" || os == "android"
 
 # HTTP layers.
 [test_resource.js]
@@ -125,20 +126,24 @@ skip-if = os == "android"
 # Firefox Accounts specific tests
 [test_fxa_startOver.js]
 [test_fxa_service_cluster.js]
 [test_fxa_node_reassignment.js]
 
 # Finally, we test each engine.
 [test_addons_engine.js]
 run-sequentially = Hardcoded port in static files.
+tags = addons
 [test_addons_reconciler.js]
+tags = addons
 [test_addons_store.js]
 run-sequentially = Hardcoded port in static files.
+tags = addons
 [test_addons_tracker.js]
+tags = addons
 [test_bookmark_batch_fail.js]
 [test_bookmark_engine.js]
 [test_bookmark_invalid.js]
 [test_bookmark_legacy_microsummaries_support.js]
 [test_bookmark_livemarks.js]
 [test_bookmark_order.js]
 [test_bookmark_places_query_rewriting.js]
 [test_bookmark_record.js]
--- a/testing/marionette/client/marionette/tests/unit/test_marionette.py
+++ b/testing/marionette/client/marionette/tests/unit/test_marionette.py
@@ -1,17 +1,28 @@
 # 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 itertools
+
 from marionette_driver import errors
-from marionette import marionette_test
+from marionette.marionette_test import MarionetteTestCase as TC
 
 
-class TestHandleError(marionette_test.MarionetteTestCase):
+class TestProtocol1Errors(TC):
+    def setUp(self):
+        TC.setUp(self)
+        self.op = self.marionette.protocol
+        self.marionette.protocol = 1
+
+    def tearDown(self):
+        self.marionette.protocol = self.op
+        TC.tearDown(self)
+
     def test_malformed_packet(self):
         for t in [{}, {"error": None}]:
             with self.assertRaisesRegexp(errors.MarionetteException, "Malformed packet"):
                 self.marionette._handle_error(t)
 
     def test_known_error_code(self):
         with self.assertRaises(errors.NoSuchElementException):
             self.marionette._handle_error(
@@ -24,8 +35,56 @@ class TestHandleError(marionette_test.Ma
 
     def test_unknown_error_code(self):
         with self.assertRaises(errors.MarionetteException):
             self.marionette._handle_error({"error": {"status": 123456}})
 
     def test_unknown_error_status(self):
         with self.assertRaises(errors.MarionetteException):
             self.marionette._handle_error({"error": {"status": "barbera"}})
+
+
+class TestProtocol2Errors(TC):
+    def setUp(self):
+        TC.setUp(self)
+        self.op = self.marionette.protocol
+        self.marionette.protocol = 2
+
+    def tearDown(self):
+        self.marionette.protocol = self.op
+        TC.tearDown(self)
+
+    def test_malformed_packet(self):
+        req = ["error", "message", "stacktrace"]
+        ps = []
+        for p in [p for i in range(0, len(req) + 1) for p in itertools.permutations(req, i)]:
+            ps.append(dict((x, None) for x in p))
+
+        for p in filter(lambda p: len(p) < 3, ps):
+            self.assertRaises(KeyError, self.marionette._handle_error, p)
+
+    def test_known_error_code(self):
+        with self.assertRaises(errors.NoSuchElementException):
+            self.marionette._handle_error(
+                {"error": errors.NoSuchElementException.code[0],
+                 "message": None,
+                 "stacktrace": None})
+
+    def test_known_error_status(self):
+        with self.assertRaises(errors.NoSuchElementException):
+            self.marionette._handle_error(
+                {"error": errors.NoSuchElementException.status,
+                 "message": None,
+                 "stacktrace": None})
+
+    def test_unknown_error_code(self):
+        with self.assertRaises(errors.MarionetteException):
+            self.marionette._handle_error(
+                {"error": 123456,
+                 "message": None,
+                 "stacktrace": None})
+
+    def test_unknown_error_status(self):
+        with self.assertRaises(errors.MarionetteException):
+            self.marionette._handle_error(
+                {"error": "barbera",
+                 "message": None,
+                 "stacktrace": None})
--- a/testing/marionette/client/marionette/tests/unit/test_teardown_context_preserved.py
+++ b/testing/marionette/client/marionette/tests/unit/test_teardown_context_preserved.py
@@ -10,12 +10,12 @@ class TestTearDownContext(MarionetteTest
         MarionetteTestCase.setUp(self)
         self.marionette.set_context(self.marionette.CONTEXT_CHROME)
 
     def tearDown(self):
         self.assertEqual(self.get_context(), self.marionette.CONTEXT_CHROME)
         MarionetteTestCase.tearDown(self)
 
     def get_context(self):
-        return self.marionette._send_message('getContext', 'value')
+        return self.marionette._send_message("getContext", key="value")
 
     def test_skipped_teardown_ok(self):
         raise SkipTest("This should leave our teardown method in chrome context")
--- a/testing/marionette/client/marionette/tests/unit/test_with_using_context.py
+++ b/testing/marionette/client/marionette/tests/unit/test_with_using_context.py
@@ -9,23 +9,23 @@ from marionette_driver.errors import Mar
 class TestSetContext(MarionetteTestCase):
     def setUp(self):
         MarionetteTestCase.setUp(self)
 
         # shortcuts to improve readability of these tests
         self.chrome = self.marionette.CONTEXT_CHROME
         self.content = self.marionette.CONTEXT_CONTENT
 
-        test_url = self.marionette.absolute_url('empty.html')
+        test_url = self.marionette.absolute_url("empty.html")
         self.marionette.navigate(test_url)
         self.marionette.set_context(self.content)
         self.assertEquals(self.get_context(), self.content)
 
     def get_context(self):
-        return self.marionette._send_message('getContext', 'value')
+        return self.marionette._send_message("getContext", key="value")
 
     def test_set_different_context_using_with_block(self):
         with self.marionette.using_context(self.chrome):
             self.assertEquals(self.get_context(), self.chrome)
         self.assertEquals(self.get_context(), self.content)
 
     def test_set_same_context_using_with_block(self):
         with self.marionette.using_context(self.content):
--- a/testing/marionette/driver/marionette_driver/application_cache.py
+++ b/testing/marionette/driver/marionette_driver/application_cache.py
@@ -1,31 +1,29 @@
-"""
-Copyright 2011 Software Freedom Conservancy.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+# Copyright 2015 Mozilla Foundation
+# Copyright 2011 Software Freedom Conservancy.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 
 class ApplicationCache(object):
-
     UNCACHED = 0
     IDLE = 1
     CHECKING = 2
     DOWNLOADING = 3
     UPDATE_READY = 4
     OBSOLETE = 5
 
     def __init__(self, driver):
         self.driver = driver
 
     @property
     def status(self):
-        return self.driver._send_message('getAppCacheStatus', 'value')
+        return self.driver._send_message("getAppCacheStatus", key="value")
--- a/testing/marionette/driver/marionette_driver/marionette.py
+++ b/testing/marionette/driver/marionette_driver/marionette.py
@@ -35,159 +35,153 @@ class HTMLElement(object):
 
     def __str__(self):
         return self.id
 
     def __eq__(self, other_element):
         return self.id == other_element.id
 
     def find_element(self, method, target):
-        '''
-        Returns an HTMLElement instance that matches the specified method and target, relative to the current element.
+        """Returns an ``HTMLElement`` instance that matches the specified
+        method and target, relative to the current element.
 
-        For more details on this function, see the find_element method in the
-        Marionette class.
-        '''
+        For more details on this function, see the `find_element` method
+        in the Marionette class.
+        """
         return self.marionette.find_element(method, target, self.id)
 
     def find_elements(self, method, target):
-        '''
-        Returns a list of all HTMLElement instances that match the specified method and target in the current context.
+        """Returns a list of all ``HTMLElement`` instances that match the
+        specified method and target in the current context.
 
-        For more details on this function, see the find_elements method in the
-        Marionette class.
-        '''
+        For more details on this function, see the find_elements method
+        in the Marionette class.
+        """
         return self.marionette.find_elements(method, target, self.id)
 
     def get_attribute(self, attribute):
-        '''
-        Returns the requested attribute (or None, if no attribute is set).
+        """Returns the requested attribute, or None if no attribute
+        is set.
 
         :param attribute: The name of the attribute.
-        '''
-        return self.marionette._send_message('getElementAttribute', 'value', id=self.id, name=attribute)
+        """
+        body = {"id": self.id, "name": attribute}
+        return self.marionette._send_message("getElementAttribute", body, key="value")
 
     def click(self):
-        return self.marionette._send_message('clickElement', 'ok', id=self.id)
+        self.marionette._send_message("clickElement", {"id": self.id})
 
     def tap(self, x=None, y=None):
-        '''
-        Simulates a set of tap events on the element.
+        """Simulates a set of tap events on the element.
 
-        :param x: X-coordinate of tap event. If not given, default to the
-         center of the element.
-        :param y: Y-coordinate of tap event. If not given, default to the
-         center of the element.
-        '''
-        return self.marionette._send_message('singleTap', 'ok', id=self.id, x=x, y=y)
+        :param x: X coordinate of tap event.  If not given, default to
+            the centre of the element.
+        :param y: Y coordinate of tap event. If not given, default to
+            the centre of the element.
+        """
+        body = {"id": self.id, "x": x, "y": y}
+        self.marionette._send_message("singleTap", body)
 
     @property
     def text(self):
-        '''
-        Returns the visible text of the element, and its child elements.
-        '''
-        return self.marionette._send_message('getElementText', 'value', id=self.id)
+        """Returns the visible text of the element, and its child elements."""
+        body = {"id": self.id}
+        return self.marionette._send_message("getElementText", body, key="value")
 
     def send_keys(self, *string):
-        '''
-        Sends the string via synthesized keypresses to the element.
-        '''
+        """Sends the string via synthesized keypresses to the element."""
         keys = Marionette.convert_keys(*string)
-        return self.marionette._send_message('sendKeysToElement', 'ok', id=self.id, value=keys)
+        body = {"id": self.id, "value": keys}
+        self.marionette._send_message("sendKeysToElement", body)
 
     def clear(self):
-        '''
-        Clears the input of the element.
-        '''
-        return self.marionette._send_message('clearElement', 'ok', id=self.id)
+        """Clears the input of the element."""
+        self.marionette._send_message("clearElement", {"id": self.id})
 
     def is_selected(self):
-        '''
-        Returns True if the element is selected.
-        '''
-        return self.marionette._send_message('isElementSelected', 'value', id=self.id)
+        """Returns True if the element is selected."""
+        body = {"id": self.id}
+        return self.marionette._send_message("isElementSelected", body, key="value")
 
     def is_enabled(self):
-        '''
-        This command will return False if all the following criteria are met otherwise return True:
+        """This command will return False if all the following criteria
+        are met otherwise return True:
 
         * A form control is disabled.
-        * A HtmlElement has a disabled boolean attribute.
-
-        '''
-        return self.marionette._send_message('isElementEnabled', 'value', id=self.id)
+        * A ``HTMLElement`` has a disabled boolean attribute.
+        """
+        body = {"id": self.id}
+        return self.marionette._send_message("isElementEnabled", body, key="value")
 
     def is_displayed(self):
-        '''
-        Returns True if the element is displayed.
-        '''
-        return self.marionette._send_message('isElementDisplayed', 'value', id=self.id)
+        """Returns True if the element is displayed, False otherwise."""
+        body = {"id": self.id}
+        return self.marionette._send_message("isElementDisplayed", body, key="value")
 
     @property
     def size(self):
         """A dictionary with the size of the element."""
         warnings.warn("The size property has been deprecated and will be removed in a future version. \
             Please use HTMLElement#rect", DeprecationWarning)
         rect = self.rect
         return {"width": rect["width"], "height": rect["height"]}
 
     @property
     def tag_name(self):
-        '''
-        The tag name of the element.
-        '''
-        return self.marionette._send_message('getElementTagName', 'value', id=self.id)
+        """The tag name of the element."""
+        body = {"id": self.id}
+        return self.marionette._send_message("getElementTagName", body, key="value")
 
     @property
     def location(self):
         """Get an element's location on the page.
 
         The returned point will contain the x and y coordinates of the
         top left-hand corner of the given element.  The point (0,0)
         refers to the upper-left corner of the document.
 
         :returns: a dictionary containing x and y as entries
-
         """
         warnings.warn("The location property has been deprecated and will be removed in a future version. \
             Please use HTMLElement#rect", DeprecationWarning)
         rect = self.rect
         return {"x": rect["x"], "y": rect["y"]}
 
     @property
     def rect(self):
         """Gets the element's bounding rectangle.
-        
+
         This will return a dictionary with the following:
 
           * x and y represent the top left coordinates of the ``HTMLElement``
             relative to top left corner of the document.
           * height and the width will contain the height and the width
             of the DOMRect of the ``HTMLElement``.
         """
-        return self.marionette._send_message("getElementRect", "value", id=self.id)
+        body = {"id": self.id}
+        return self.marionette._send_message(
+            "getElementRect", body, key="value" if self.marionette.protocol == 1 else None)
 
     def value_of_css_property(self, property_name):
-        '''
-        Gets the value of the specified CSS property name.
+        """Gets the value of the specified CSS property name.
 
         :param property_name: Property name to get the value of.
-        '''
-        return self.marionette._send_message('getElementValueOfCssProperty', 'value',
-                                             id=self.id,
-                                             propertyName=property_name)
+        """
+        body = {"id": self.id, "propertyName": property_name}
+        return self.marionette._send_message(
+            "getElementValueOfCssProperty", body, key="value")
+
 
 class MouseButton(object):
-    '''
-    Enum-like class for mouse button constants
-    '''
+    """Enum-like class for mouse button constants."""
     LEFT = 0
     MIDDLE = 1
     RIGHT = 2
 
+
 class Actions(object):
     '''
     An Action object represents a set of actions that are executed in a particular order.
 
     All action methods (press, etc.) return the Actions object itself, to make
     it easy to create a chain of events.
 
     Example usage:
@@ -441,20 +435,20 @@ class Actions(object):
         Perform a "keyUp" action for the given key code. Modifier keys are
         respected by the server for the course of an action chain.
         :param key_up: The key to release as a result of this action.
         """
         self.action_chain.append(['keyUp', key_code])
         return self
 
     def perform(self):
-        '''
-        Sends the action chain built so far to the server side for execution and clears the current chain of actions.
-        '''
-        self.current_id = self.marionette._send_message('actionChain', 'value', chain=self.action_chain, nextId=self.current_id)
+        """Sends the action chain built so far to the server side for
+        execution and clears the current chain of actions."""
+        body = {"chain": self.action_chain, "nextId": self.current_id}
+        self.current_id = self.marionette._send_message("actionChain", body, key="value")
         self.action_chain = []
         return self
 
 class MultiActions(object):
     '''
     A MultiActions object represents a sequence of actions that may be
     performed at the same time. Its intent is to allow the simulation
     of multi-touch gestures.
@@ -486,62 +480,55 @@ class MultiActions(object):
         :param action: An Actions object.
         '''
         self.multi_actions.append(action.action_chain)
         if len(action.action_chain) > self.max_length:
           self.max_length = len(action.action_chain)
         return self
 
     def perform(self):
-        '''
-        Perform all the actions added to this object.
-        '''
-        return self.marionette._send_message('multiAction', 'ok', value=self.multi_actions, max_length=self.max_length)
+        """Perform all the actions added to this object."""
+        body = {"value": self.multi_actions, "max_length": self.max_length}
+        self.marionette._send_message("multiAction", body)
+
 
 class Alert(object):
-    '''
-    A class for interacting with alerts.
+    """A class for interacting with alerts.
 
     ::
 
-      Alert(marionette).accept()
-      Alert(merionette).dismiss()
-    '''
+        Alert(marionette).accept()
+        Alert(merionette).dismiss()
+    """
 
     def __init__(self, marionette):
         self.marionette = marionette
 
     def accept(self):
-        """Accept a currently displayed modal dialog.
-        """
-        self.marionette._send_message('acceptDialog', 'ok')
+        """Accept a currently displayed modal dialog."""
+        self.marionette._send_message("acceptDialog")
 
     def dismiss(self):
-        """Dismiss a currently displayed modal dialog.
-        """
-        self.marionette._send_message('dismissDialog', 'ok')
+        """Dismiss a currently displayed modal dialog."""
+        self.marionette._send_message("dismissDialog")
 
     @property
     def text(self):
-        """Return the currently displayed text in a tab modal.
-        """
-        return self.marionette._send_message('getTextFromDialog', 'value')
+        """Return the currently displayed text in a tab modal."""
+        return self.marionette._send_message("getTextFromDialog", key="value")
 
     def send_keys(self, *string):
         """Send keys to the currently displayed text input area in an open
-        tab modal dialog.
-        """
-        keys = Marionette.convert_keys(*string)
-        self.marionette._send_message('sendKeysToDialog', 'ok', value=keys)
+        tab modal dialog."""
+        body = {"value": Marionette.convert_keys(*string)}
+        self.marionette._send_message("sendKeysToDialog", body)
 
 
 class Marionette(object):
-    """
-    Represents a Marionette connection to a browser or device.
-    """
+    """Represents a Marionette connection to a browser or device."""
 
     CONTEXT_CHROME = 'chrome' # non-browser content: windows, dialogs, etc.
     CONTEXT_CONTENT = 'content' # browser content: iframes, divs, etc.
     TIMEOUT_SEARCH = 'implicit'
     TIMEOUT_SCRIPT = 'script'
     TIMEOUT_PAGE = 'page load'
 
     def __init__(self, host='localhost', port=2828, app=None, app_args=None, bin=None,
@@ -675,95 +662,99 @@ class Marionette(object):
             s.close()
 
     def wait_for_port(self, timeout=60):
         return MarionetteTransport.wait_for_port(self.host,
                                                  self.port,
                                                  timeout=timeout)
 
     @do_crash_check
-    def _send_message(self, command, response_key="ok", **kwargs):
+    def _send_message(self, command, body=None, key=None):
         if not self.session_id and command != "newSession":
             raise errors.MarionetteException("Please start a session")
 
         message = {"name": command}
-        if self.session_id:
-            message["sessionId"] = self.session_id
-        if kwargs:
-            message["parameters"] = kwargs
+        if body:
+            message["parameters"] = body
+
+        packet = json.dumps(message)
 
         try:
-            response = self.client.send(message)
+            resp = self.client.send(packet)
         except IOError:
             if self.instance and not hasattr(self.instance, 'detached'):
                 # If we've launched the binary we've connected to, wait
                 # for it to shut down.
                 returncode = self.instance.runner.wait()
                 raise IOError("process died with returncode %d" % returncode)
             raise
         except socket.timeout:
             self.session = None
             self.window = None
             self.client.close()
             raise errors.TimeoutException("Connection timed out")
 
         # Process any emulator commands that are sent from a script
-        # while it's executing.
-        while True:
-            if response.get("emulator_cmd"):
-                response = self._handle_emulator_cmd(response)
-                continue;
-
-            if response.get("emulator_shell"):
-                response = self._handle_emulator_shell(response)
-                continue;
+        # while it's executing
+        if isinstance(resp, dict) and any (k in resp for k in ("emulator_cmd", "emulator_shell")):
+            while True:
+                id = resp.get("id")
+                cmd = resp.get("emulator_cmd")
+                shell = resp.get("emulator_shell")
+                if cmd:
+                    resp = self._emulator_cmd(id, cmd)
+                    continue
+                if shell:
+                    resp = self._emulator_shell(id, shell)
+                    continue
+                break
 
-            break;
-        if not self.session_id:
-            self.session_id = response.get("sessionId", None)
+        if "error" in resp:
+            self._handle_error(resp)
 
-        if response_key in response:
-            return response[response_key]
-        self._handle_error(response)
+        if key is not None:
+            resp = resp[key]
+        return resp
 
-    def _handle_emulator_cmd(self, response):
-        cmd = response.get("emulator_cmd")
-        if not cmd or not self.emulator:
+    def _emulator_cmd(self, id, cmd):
+        if not self.emulator:
             raise errors.MarionetteException(
                 "No emulator in this test to run command against")
-        cmd = cmd.encode("ascii")
-        result = self.emulator._run_telnet(cmd)
-        return self.client.send({"name": "emulatorCmdResult",
-                                 "id": response.get("id"),
-                                 "result": result})
+        payload = cmd.encode("ascii")
+        result = self.emulator._run_telnet(payload)
+        return self._send_emulator_result(id, result)
 
-    def _handle_emulator_shell(self, response):
-        args = response.get("emulator_shell")
-        if not isinstance(args, list) or not self.emulator:
+    def _emulator_shell(self, id, args):
+        if not self.emulator:
             raise errors.MarionetteException(
                 "No emulator in this test to run shell command against")
         buf = StringIO.StringIO()
         self.emulator.dm.shell(args, buf)
         result = str(buf.getvalue()[0:-1]).rstrip().splitlines()
         buf.close()
+        return self._send_emulator_result(id, result)
+
+    def _send_emulator_result(self, id, result):
         return self.client.send({"name": "emulatorCmdResult",
-                                 "id": response.get("id"),
+                                 "id": id,
                                  "result": result})
 
-    def _handle_error(self, response):
-        if "error" not in response or not isinstance(response["error"], dict):
-            raise errors.MarionetteException(
-                "Malformed packet, expected key 'error' to be a dict: %s" % response)
-
-        error = response["error"]
-        status = error.get("status")
-        message = error.get("message")
-        stacktrace = error.get("stacktrace")
-
-        raise errors.lookup(status)(message, stacktrace=stacktrace)
+    def _handle_error(self, resp):
+        if self.protocol == 1:
+            if "error" not in resp or not isinstance(resp["error"], dict):
+                raise errors.MarionetteException(
+                    "Malformed packet, expected key 'error' to be a dict: %s" % resp)
+            error = resp["error"].get("status")
+            message = resp["error"].get("message")
+            stacktrace = resp["error"].get("stacktrace")
+        else:
+            error = resp["error"]
+            message = resp["message"]
+            stacktrace = resp["stacktrace"]
+        raise errors.lookup(error)(message, stacktrace=stacktrace)
 
     def _reset_timeouts(self):
         if self.timeout is not None:
             self.timeouts(self.TIMEOUT_SEARCH, self.timeout)
             self.timeouts(self.TIMEOUT_SCRIPT, self.timeout)
             self.timeouts(self.TIMEOUT_PAGE, self.timeout)
         else:
             self.timeouts(self.TIMEOUT_PAGE, 30000)
@@ -979,17 +970,17 @@ class Marionette(object):
             if clean:
                 raise ValueError
             # Values here correspond to constants in nsIAppStartup.
             # See https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup
             restart_flags = [
                 "eForceQuit",
                 "eRestart",
             ]
-            self._send_message('quitApplication', flags=restart_flags)
+            self._send_message("quitApplication", {"flags": restart_flags})
             self.client.close()
             # The instance is restarting itself; we will no longer be able to
             # track it by pid, so mark it as 'detached'.
             self.instance.detached = True
         else:
             self.delete_session()
             self.instance.restart(clean=clean)
         assert(self.wait_for_port()), "Timed out waiting for port!"
@@ -1016,261 +1007,247 @@ class Marionette(object):
             passed in then one will be generated by the marionette server.
 
         :returns: A dict of the capabilities offered."""
         if self.instance:
             returncode = self.instance.runner.process_handler.proc.returncode
             if returncode is not None:
                 # We're managing a binary which has terminated, so restart it.
                 self.instance.restart()
+
+        self.protocol, _ = self.client.connect()
         self.wait_for_port(timeout=timeout)
-        self.session = self._send_message('newSession', 'value', capabilities=desired_capabilities, sessionId=session_id)
-        self.b2g = 'b2g' in self.session
+
+        body = {"capabilities": desired_capabilities, "sessionId": session_id}
+        resp = self._send_message("newSession", body)
+
+        self.session_id = resp["sessionId"]
+        self.session = resp["value"] if self.protocol == 1 else resp["capabilities"]
+        self.b2g = "b2g" in self.session
+
         return self.session
 
     @property
     def test_name(self):
         return self._test_name
 
     @test_name.setter
     def test_name(self, test_name):
-        if self._send_message('setTestName', 'ok', value=test_name):
+        if self._send_message("setTestName", {"value": test_name}):
             self._test_name = test_name
 
     def delete_session(self):
         """Close the current session and disconnect from the server."""
-        response = self._send_message('deleteSession', 'ok')
+        self._send_message("deleteSession")
         self.session_id = None
         self.session = None
         self.window = None
         self.client.close()
-        return response
 
     @property
     def session_capabilities(self):
         '''
         A JSON dictionary representing the capabilities of the current session.
         '''
         return self.session
 
     def set_script_timeout(self, timeout):
-        '''
-        Sets the maximum number of ms that an asynchronous script is allowed to run.
+        """Sets the maximum number of ms that an asynchronous script is
+        allowed to run.
 
-        If a script does not return in the specified amount of time, a
-        ScriptTimeoutException is raised.
+        If a script does not return in the specified amount of time,
+        a ScriptTimeoutException is raised.
 
         :param timeout: The maximum number of milliseconds an asynchronous
-         script can run without causing an ScriptTimeoutException to be raised
-        '''
-        response = self._send_message('setScriptTimeout', 'ok', ms=timeout)
-        return response
+            script can run without causing an ScriptTimeoutException to
+            be raised
+        """
+        self._send_message("setScriptTimeout", {"ms": timeout})
 
     def set_search_timeout(self, timeout):
-        '''
-        Sets a timeout for the find methods.
+        """Sets a timeout for the find methods.
 
-        When searching for an element using either
-        :class:`Marionette.find_element` or :class:`Marionette.find_elements`,
-        the method will continue trying to locate the element for up to timeout
-        ms. This can be useful if, for example, the element you're looking for
-        might not exist immediately, because it belongs to a page which is
+        When searching for an element using
+        either :class:`Marionette.find_element` or
+        :class:`Marionette.find_elements`, the method will continue
+        trying to locate the element for up to timeout ms. This can be
+        useful if, for example, the element you're looking for might
+        not exist immediately, because it belongs to a page which is
         currently being loaded.
 
         :param timeout: Timeout in milliseconds.
-        '''
-        response = self._send_message('setSearchTimeout', 'ok', ms=timeout)
-        return response
+        """
+        self._send_message("setSearchTimeout", {"ms": timeout})
 
     @property
     def current_window_handle(self):
         """Get the current window's handle.
 
         Returns an opaque server-assigned identifier to this window
         that uniquely identifies it within this Marionette instance.
         This can be used to switch to this window at a later point.
 
         :returns: unique window handle
         :rtype: string
-
         """
-        self.window = self._send_message("getWindowHandle", "value")
+        self.window = self._send_message("getWindowHandle", key="value")
         return self.window
 
     @property
     def current_chrome_window_handle(self):
         """Get the current chrome window's handle. Corresponds to
         a chrome window that may itself contain tabs identified by
         window_handles.
 
         Returns an opaque server-assigned identifier to this window
         that uniquely identifies it within this Marionette instance.
         This can be used to switch to this window at a later point.
 
         :returns: unique window handle
         :rtype: string
-
         """
-        self.chrome_window = self._send_message("getCurrentChromeWindowHandle", "value")
+        self.chrome_window = self._send_message(
+            "getCurrentChromeWindowHandle", key="value")
         return self.chrome_window
 
-
     def get_window_position(self):
-        """Get the current window's position
-           Return a dictionary with the keys x and y
-           :returns: a dictionary with x and y
+        """Get the current window's position.
+
+        :returns: a dictionary with x and y
         """
-        return self._send_message("getWindowPosition", "value")
+        return self._send_message(
+            "getWindowPosition", key="value" if self.protocol == 1 else None)
 
     def set_window_position(self, x, y):
+        """Set the position of the current window
+
+        :param x: x coordinate for the top left of the window
+        :param y: y coordinate for the top left of the window
         """
-           Set the position of the current window
-            :param x: x coordinate for the top left of the window
-            :param y: y coordinate for the top left of the window
-        """
-        response = self._send_message("setWindowPosition", "ok", x=x, y=y)
-        return response
+        self._send_message("setWindowPosition", {"x": x, "y": y})
 
     @property
     def title(self):
-        '''
-        Current title of the active window.
-        '''
-        response = self._send_message('getTitle', 'value')
-        return response
+        """Current title of the active window."""
+        return self._send_message("getTitle", key="value")
 
     @property
     def window_handles(self):
         """Get list of windows in the current context.
 
         If called in the content context it will return a list of
         references to all available browser windows.  Called in the
         chrome context, it will list all available windows, not just
         browser windows (e.g. not just navigator.browser).
 
         Each window handle is assigned by the server, and the list of
         strings returned does not have a guaranteed ordering.
 
         :returns: unordered list of unique window handles as strings
-
         """
-
-        response = self._send_message("getWindowHandles", "value")
-        return response
+        return self._send_message(
+            "getWindowHandles", key="value" if self.protocol == 1 else None)
 
     @property
     def chrome_window_handles(self):
         """Get a list of currently open chrome windows.
 
         Each window handle is assigned by the server, and the list of
         strings returned does not have a guaranteed ordering.
 
         :returns: unordered list of unique window handles as strings
-
         """
-
-        response = self._send_message("getChromeWindowHandles", "value")
-        return response
-
+        return self._send_message(
+            "getChromeWindowHandles", key="value" if self.protocol == 1 else None)
 
     @property
     def page_source(self):
-        '''
-        A string representation of the DOM.
-        '''
-        response = self._send_message('getPageSource', 'value')
-        return response
+        """A string representation of the DOM."""
+        return self._send_message("getPageSource", key="value")
+
     def close(self):
         """Close the current window, ending the session if it's the last
         window currently open.
 
         On B2G this method is a noop and will return immediately.
-
         """
-
-        response = self._send_message("close", "ok")
-        return response
+        self._send_message("close")
 
     def close_chrome_window(self):
         """Close the currently selected chrome window, ending the session
         if it's the last window open.
 
         On B2G this method is a noop and will return immediately.
-
         """
-
-        response = self._send_message("closeChromeWindow", "ok")
-        return response
+        self._send_message("closeChromeWindow")
 
     def set_context(self, context):
-        '''
-        Sets the context that Marionette commands are running in.
+        """Sets the context that Marionette commands are running in.
 
         :param context: Context, may be one of the class properties
-         `CONTEXT_CHROME` or `CONTEXT_CONTENT`.
+            `CONTEXT_CHROME` or `CONTEXT_CONTENT`.
 
         Usage example::
 
-          marionette.set_context(marionette.CONTEXT_CHROME)
-        '''
+            marionette.set_context(marionette.CONTEXT_CHROME)
+        """
         assert(context == self.CONTEXT_CHROME or context == self.CONTEXT_CONTENT)
-        return self._send_message('setContext', 'ok', value=context)
+        if context not in [self.CONTEXT_CHROME, self.CONTEXT_CONTENT]:
+            raise ValueError("Unknown context: %s" % context)
+        self._send_message("setContext", {"value": context})
 
     @contextmanager
     def using_context(self, context):
-        '''
-        Sets the context that Marionette commands are running in using
+        """Sets the context that Marionette commands are running in using
         a `with` statement. The state of the context on the server is
         saved before entering the block, and restored upon exiting it.
 
         :param context: Context, may be one of the class properties
-         `CONTEXT_CHROME` or `CONTEXT_CONTENT`.
+            `CONTEXT_CHROME` or `CONTEXT_CONTENT`.
 
         Usage example::
 
-          with marionette.using_context(marionette.CONTEXT_CHROME):
-              # chrome scope
-              ... do stuff ...
-        '''
-        scope = self._send_message('getContext', 'value')
+            with marionette.using_context(marionette.CONTEXT_CHROME):
+                # chrome scope
+                ... do stuff ...
+        """
+        scope = self._send_message("getContext", key="value")
         self.set_context(context)
         try:
             yield
         finally:
             self.set_context(scope)
 
     def switch_to_alert(self):
-        '''
-        Returns an Alert object for interacting with a currently displayed alert.
+        """Returns an Alert object for interacting with a currently
+        displayed alert.
 
         ::
 
             alert = self.marionette.switch_to_alert()
             text = alert.text
             alert.accept()
-        '''
+        """
         return Alert(self)
 
     def switch_to_window(self, window_id):
-        '''
-        Switch to the specified window; subsequent commands will be directed at the new window.
+        """Switch to the specified window; subsequent commands will be
+        directed at the new window.
 
         :param window_id: The id or name of the window to switch to.
-        '''
-        response = self._send_message('switchToWindow', 'ok', name=window_id)
+        """
+        self._send_message("switchToWindow", {"name": window_id})
         self.window = window_id
-        return response
 
     def get_active_frame(self):
-        '''
-        Returns an HTMLElement representing the frame Marionette is currently acting on.
-        '''
-        response = self._send_message('getActiveFrame', 'value')
-        if response:
-            return HTMLElement(self, response)
+        """Returns an HTMLElement representing the frame Marionette is
+        currently acting on."""
+        element = self._send_message("getActiveFrame", key="value")
+        if element:
+            return HTMLElement(self, element)
         return None
 
     def switch_to_default_content(self):
         """Switch the current context to page's default content."""
         return self.switch_to_frame()
 
     def switch_to_frame(self, frame=None, focus=True):
         """Switch the current context to the specified frame. Subsequent
@@ -1280,49 +1257,45 @@ class Marionette(object):
         :param frame: A reference to the frame to switch to.  This can
             be an ``HTMLElement``, an integer index, string name, or an
             ID attribute.  If you call ``switch_to_frame`` without an
             argument, it will switch to the top-level frame.
 
         :param focus: A boolean value which determins whether to focus
             the frame that we just switched to.
         """
-        kwargs = {"focus": focus}
+        body = {"focus": focus}
         if isinstance(frame, HTMLElement):
-            kwargs["element"] = frame.id
+            body["element"] = frame.id
         elif frame is not None:
-            kwargs["id"] = frame
-        return self._send_message("switchToFrame", "ok", **kwargs)
+            body["id"] = frame
+        self._send_message("switchToFrame", body)
 
     def get_url(self):
         """Get a string representing the current URL.
 
         On Desktop this returns a string representation of the URL of
         the current top level browsing context.  This is equivalent to
         document.location.href.
 
         When in the context of the chrome, this returns the canonical
         URL of the current resource.
 
         :returns: string representation of URL
-
         """
-
-        response = self._send_message("getCurrentUrl", "value")
-        return response
+        return self._send_message("getCurrentUrl", key="value")
 
     def get_window_type(self):
-        '''
-        Gets the windowtype attribute of the window Marionette is currently acting on.
+        """Gets the windowtype attribute of the window Marionette is
+        currently acting on.
 
         This command only makes sense in a chrome context. You might use this
         method to distinguish a browser window from an editor window.
-        '''
-        response = self._send_message('getWindowType', 'value')
-        return response
+        """
+        return self._send_message("getWindowType", key="value")
 
     def navigate(self, url):
         """Navigate to given `url`.
 
         Navigates the current top-level browsing context's content
         frame to the given URL and waits for the document to load or
         the session's page timeout duration to elapse before returning.
 
@@ -1337,440 +1310,446 @@ class Marionette(object):
         `window` triggers and `document.readState` is "complete".
 
         In chrome context it will change the current `window`'s location
         to the supplied URL and wait until `document.readState` equals
         "complete" or the page timeout duration has elapsed.
 
         :param url: The URL to navigate to.
         """
-        return self._send_message("get", "ok", url=url)
+        self._send_message("get", {"url": url})
 
     def timeouts(self, timeout_type, ms):
-        """An interface for managing timeout behaviour of a Marionette instance.
+        """An interface for managing timeout behaviour of a Marionette
+        instance.
+
+        Setting timeouts specifies the type and amount of time the
+        Marionette instance should wait during requests.
 
-        Setting timeouts specifies the type and amount of time the Marionette instance should wait during requests.
+        There are three types of timeouts that can be set: implicit,
+        script and page load.
 
-        There are three types of timeouts that can be set: implicit, script and page load.
+        * An implicit  timeout specifies the amount of time a Marionette
+        instance should wait when searching for elements. Here, marionette
+        polls a page until an element is found or the timeout expires,
+        whichever occurs first. When searching for multiple elements,
+        the driver should poll the page until at least one element is
+        found or the timeout expires, at which point it should return
+        an empty list.
 
-        * An implicit  timeout specifies the amount of time a Marionette instance should wait when searching for elements. Here, marionette polls a page until an element is found or the timeout expires, whichever occurs first. When searching for multiple elements, the driver should poll the page until at least one element is found or the timeout expires, at which point it should return an empty list.
-        * A script timeout specifies the amount of time the Marionette instance should wait after calling executeAsyncScript for the callback to have executed before returning a timeout response.
-        * A page load timeout specifies the amount of time the Marionette instance should wait for a page load operation to complete. If this limit is exceeded, the Marionette instance will return a "timeout" response status.
+        * A script timeout specifies the amount of time the Marionette
+        instance should wait after calling executeAsyncScript for the
+        callback to have executed before returning a timeout response.
+
+        * A page load timeout specifies the amount of time the Marionette
+        instance should wait for a page load operation to complete. If
+        this limit is exceeded, the Marionette instance will return a
+        "timeout" response status.
 
-        :param timeout_type: A string value specifying the timeout type. This must be one of three types: 'implicit', 'script' or 'page load'
-        :param ms: A number value specifying the timeout length in milliseconds (ms)
+        :param timeout_type: A string value specifying the timeout
+            type. This must be one of three types: 'implicit', 'script'
+            or 'page load'
+        :param ms: A number value specifying the timeout length in
+            milliseconds (ms)
         """
-        assert(timeout_type == self.TIMEOUT_SEARCH or timeout_type == self.TIMEOUT_SCRIPT or timeout_type == self.TIMEOUT_PAGE)
-        response = self._send_message('timeouts', 'ok', type=timeout_type, ms=ms)
-        return response
+        if timeout_type not in [self.TIMEOUT_SEARCH, self.TIMEOUT_SCRIPT, self.TIMEOUT_PAGE]:
+            raise ValueError("Unknown timeout type: %s" % timeout_type)
+        body = {"type": timeout_type, "ms": ms}
+        self._send_message("timeouts", body)
 
     def go_back(self):
-        '''
-        Causes the browser to perform a back navigation.
-        '''
-        response = self._send_message('goBack', 'ok')
-        return response
+        """Causes the browser to perform a back navigation."""
+        self._send_message("goBack")
 
     def go_forward(self):
-        '''
-        Causes the browser to perform a forward navigation.
-        '''
-        response = self._send_message('goForward', 'ok')
-        return response
+        """Causes the browser to perform a forward navigation."""
+        self._send_message("goForward")
 
     def refresh(self):
-        '''
-        Causes the browser to perform to refresh the current page.
-        '''
-        response = self._send_message('refresh', 'ok')
-        return response
+        """Causes the browser to perform to refresh the current page."""
+        self._send_message("refresh")
 
     def wrapArguments(self, args):
         if isinstance(args, list):
             wrapped = []
             for arg in args:
                 wrapped.append(self.wrapArguments(arg))
         elif isinstance(args, dict):
             wrapped = {}
             for arg in args:
                 wrapped[arg] = self.wrapArguments(args[arg])
         elif type(args) == HTMLElement:
-            wrapped = {'element-6066-11e4-a52e-4f735466cecf': args.id,
-                       'ELEMENT': args.id }
+            wrapped = {"element-6066-11e4-a52e-4f735466cecf": args.id,
+                       "ELEMENT": args.id}
         elif (isinstance(args, bool) or isinstance(args, basestring) or
               isinstance(args, int) or isinstance(args, float) or args is None):
             wrapped = args
-
         return wrapped
 
     def unwrapValue(self, value):
         if isinstance(value, list):
             unwrapped = []
             for item in value:
                 unwrapped.append(self.unwrapValue(item))
         elif isinstance(value, dict):
             unwrapped = {}
             for key in value:
-                if key == 'element-6066-11e4-a52e-4f735466cecf':
+                if key == "element-6066-11e4-a52e-4f735466cecf":
                     unwrapped = HTMLElement(self, value[key])
                     break
-                elif key == 'ELEMENT':
+                elif key == "ELEMENT":
                     unwrapped = HTMLElement(self, value[key])
                     break
                 else:
                     unwrapped[key] = self.unwrapValue(value[key])
         else:
             unwrapped = value
-
         return unwrapped
 
     def execute_js_script(self, script, script_args=None, async=True,
                           new_sandbox=True, script_timeout=None,
                           inactivity_timeout=None, filename=None,
                           sandbox='default'):
         if script_args is None:
             script_args = []
         args = self.wrapArguments(script_args)
-        response = self._send_message('executeJSScript',
-                                      'value',
-                                      script=script,
-                                      args=args,
-                                      async=async,
-                                      newSandbox=new_sandbox,
-                                      scriptTimeout=script_timeout,
-                                      inactivityTimeout=inactivity_timeout,
-                                      filename=filename,
-                                      line=None)
-        return self.unwrapValue(response)
+        body = {"script": script,
+                "args": args,
+                "async": async,
+                "newSandbox": new_sandbox,
+                "scriptTimeout": script_timeout,
+                "inactivityTimeout": inactivity_timeout,
+                "filename": filename,
+                "line": None}
+        rv = self._send_message("executeJSScript", body, key="value")
+        return self.unwrapValue(rv)
 
     def execute_script(self, script, script_args=None, new_sandbox=True,
-                       sandbox='default', script_timeout=None):
-        '''
-        Executes a synchronous JavaScript script, and returns the result (or None if the script does return a value).
+                       sandbox="default", script_timeout=None):
+        """Executes a synchronous JavaScript script, and returns the
+        result (or None if the script does return a value).
 
         The script is executed in the context set by the most recent
         set_context() call, or to the CONTEXT_CONTENT context if set_context()
         has not been called.
 
         :param script: A string containing the JavaScript to execute.
         :param script_args: A list of arguments to pass to the script.
-        :param sandbox: A tag referring to the sandbox you wish to use; if
-         you specify a new tag, a new sandbox will be created.  If you use the
-         special tag 'system', the sandbox will be created using the system
-         principal which has elevated privileges.
-        :param new_sandbox: If False, preserve global variables from the last
-         execute_*script call. This is True by default, in which case no
-         globals are preserved.
+        :param sandbox: A tag referring to the sandbox you wish to use;
+            if you specify a new tag, a new sandbox will be created.
+            If you use the special tag `system`, the sandbox will
+            be created using the system principal which has elevated
+            privileges.
+        :param new_sandbox: If False, preserve global variables from
+            the last execute_*script call. This is True by default, in which
+            case no globals are preserved.
 
         Simple usage example:
 
         ::
 
-          result = marionette.execute_script("return 1;")
-          assert result == 1
+            result = marionette.execute_script("return 1;")
+            assert result == 1
 
         You can use the `script_args` parameter to pass arguments to the
         script:
 
         ::
 
-          result = marionette.execute_script("return arguments[0] + arguments[1];",
-                                             script_args=[2, 3])
-          assert result == 5
-          some_element = marionette.find_element("id", "someElement")
-          sid = marionette.execute_script("return arguments[0].id;", script_args=[some_element])
-          assert some_element.get_attribute("id") == sid
+            result = marionette.execute_script("return arguments[0] + arguments[1];",
+                                               script_args=[2, 3])
+            assert result == 5
+            some_element = marionette.find_element("id", "someElement")
+            sid = marionette.execute_script("return arguments[0].id;", script_args=[some_element])
+            assert some_element.get_attribute("id") == sid
 
-        Scripts wishing to access non-standard properties of the window object must use
-        window.wrappedJSObject:
+        Scripts wishing to access non-standard properties of the window
+        object must use window.wrappedJSObject:
 
         ::
 
-          result = marionette.execute_script("""
-            window.wrappedJSObject.test1 = 'foo';
-            window.wrappedJSObject.test2 = 'bar';
-            return window.wrappedJSObject.test1 + window.wrappedJSObject.test2;
-            """)
-          assert result == "foobar"
+            result = marionette.execute_script('''
+              window.wrappedJSObject.test1 = "foo";
+              window.wrappedJSObject.test2 = "bar";
+              return window.wrappedJSObject.test1 + window.wrappedJSObject.test2;
+              ''')
+            assert result == "foobar"
 
         Global variables set by individual scripts do not persist between
-        script calls by default.  If you wish to persist data between script
-        calls, you can set new_sandbox to False on your next call, and add any
-        new variables to a new 'global' object like this:
+        script calls by default.  If you wish to persist data between
+        script calls, you can set new_sandbox to False on your next call,
+        and add any new variables to a new 'global' object like this:
 
         ::
 
-          marionette.execute_script("global.test1 = 'foo';")
-          result = self.marionette.execute_script("return global.test1;", new_sandbox=False)
-          assert result == 'foo'
+            marionette.execute_script("global.test1 = 'foo';")
+            result = self.marionette.execute_script("return global.test1;", new_sandbox=False)
+            assert result == "foo"
 
-        '''
+        """
         if script_args is None:
             script_args = []
         args = self.wrapArguments(script_args)
         stack = traceback.extract_stack()
         frame = stack[-2:-1][0] # grab the second-to-last frame
-        response = self._send_message('executeScript',
-                                      'value',
-                                      script=script,
-                                      args=args,
-                                      newSandbox=new_sandbox,
-                                      sandbox=sandbox,
-                                      scriptTimeout=script_timeout,
-                                      line=int(frame[1]),
-                                      filename=os.path.basename(frame[0]))
-        return self.unwrapValue(response)
+        body = {"script": script,
+                "args": args,
+                "newSandbox": new_sandbox,
+                "sandbox": sandbox,
+                "scriptTimeout": script_timeout,
+                "line": int(frame[1]),
+                "filename": os.path.basename(frame[0])}
+        rv = self._send_message("executeScript", body, key="value")
+        return self.unwrapValue(rv)
 
     def execute_async_script(self, script, script_args=None, new_sandbox=True,
-                             sandbox='default', script_timeout=None,
+                             sandbox="default", script_timeout=None,
                              debug_script=False):
-        '''
-        Executes an asynchronous JavaScript script, and returns the result (or None if the script does return a value).
+        """Executes an asynchronous JavaScript script, and returns the
+        result (or None if the script does return a value).
 
         The script is executed in the context set by the most recent
-        set_context() call, or to the CONTEXT_CONTENT context if set_context()
-        has not been called.
+        set_context() call, or to the CONTEXT_CONTENT context if
+        set_context() has not been called.
 
         :param script: A string containing the JavaScript to execute.
         :param script_args: A list of arguments to pass to the script.
         :param sandbox: A tag referring to the sandbox you wish to use; if
-         you specify a new tag, a new sandbox will be created.  If you use the
-         special tag 'system', the sandbox will be created using the system
-         principal which has elevated privileges.
-        :param new_sandbox: If False, preserve global variables from the last
-         execute_*script call. This is True by default, in which case no
-         globals are preserved.
+            you specify a new tag, a new sandbox will be created.  If you
+            use the special tag `system`, the sandbox will be created
+            using the system principal which has elevated privileges.
+        :param new_sandbox: If False, preserve global variables from
+            the last execute_*script call. This is True by default,
+            in which case no globals are preserved.
         :param debug_script: Capture javascript exceptions when in
-         CONTEXT_CHROME context.
+            `CONTEXT_CHROME` context.
 
         Usage example:
 
         ::
 
-          marionette.set_script_timeout(10000) # set timeout period of 10 seconds
-          result = self.marionette.execute_async_script("""
-            // this script waits 5 seconds, and then returns the number 1
-            setTimeout(function() {
-              marionetteScriptFinished(1);
-            }, 5000);
-          """)
-          assert result == 1
-        '''
+            marionette.set_script_timeout(10000) # set timeout period of 10 seconds
+            result = self.marionette.execute_async_script('''
+              // this script waits 5 seconds, and then returns the number 1
+              setTimeout(function() {
+                marionetteScriptFinished(1);
+              }, 5000);
+            ''')
+            assert result == 1
+        """
         if script_args is None:
             script_args = []
         args = self.wrapArguments(script_args)
         stack = traceback.extract_stack()
         frame = stack[-2:-1][0] # grab the second-to-last frame
-        response = self._send_message('executeAsyncScript',
-                                      'value',
-                                      script=script,
-                                      args=args,
-                                      newSandbox=new_sandbox,
-                                      sandbox=sandbox,
-                                      scriptTimeout=script_timeout,
-                                      line=int(frame[1]),
-                                      filename=os.path.basename(frame[0]),
-                                      debug_script=debug_script)
-        return self.unwrapValue(response)
+        body = {"script": script,
+                "args": args,
+                "newSandbox": new_sandbox,
+                "sandbox": sandbox,
+                "scriptTimeout": script_timeout,
+                "line": int(frame[1]),
+                "filename": os.path.basename(frame[0]),
+                "debug_script": debug_script}
+        rv = self._send_message("executeAsyncScript", body, key="value")
+        return self.unwrapValue(rv)
 
     def find_element(self, method, target, id=None):
-        '''
-        Returns an HTMLElement instances that matches the specified method and target in the current context.
+        """Returns an HTMLElement instances that matches the specified
+        method and target in the current context.
 
         An HTMLElement instance may be used to call other methods on the
         element, such as click().  If no element is immediately found, the
         attempt to locate an element will be repeated for up to the amount of
         time set by set_search_timeout(). If multiple elements match the given
         criteria, only the first is returned. If no element matches, a
         NoSuchElementException will be raised.
 
-        :param method: The method to use to locate the element; one of: "id",
-                       "name", "class name", "tag name", "css selector", "link text",
-                       "partial link text", "xpath", "anon" and "anon attribute".
-                       Note that the "name", "link text" and
-                       "partial link test" methods are not supported in the chrome dom.
+        :param method: The method to use to locate the element; one of:
+            "id", "name", "class name", "tag name", "css selector",
+            "link text", "partial link text", "xpath", "anon" and "anon
+            attribute". Note that the "name", "link text" and "partial
+            link test" methods are not supported in the chrome DOM.
         :param target: The target of the search.  For example, if method =
-                       "tag", target might equal "div".  If method = "id", target would be
-                       an element id.
+            "tag", target might equal "div".  If method = "id", target would
+            be an element id.
         :param id: If specified, search for elements only inside the element
-                   with the specified id.
-        '''
-        kwargs = { 'value': target, 'using': method }
+            with the specified id.
+        """
+        body = {"value": target, "using": method}
         if id:
-            kwargs['element'] = id
-        response = self._send_message('findElement', 'value', **kwargs)
-        element = HTMLElement(self, response['ELEMENT'])
-        return element
+            body["element"] = id
+        el = self._send_message("findElement", body, key="value")
+        ref = el["ELEMENT"]
+        return HTMLElement(self, ref)
 
     def find_elements(self, method, target, id=None):
-        '''
-        Returns a list of all HTMLElement instances that match the specified method and target in the current context.
+        """Returns a list of all HTMLElement instances that match the
+        specified method and target in the current context.
 
         An HTMLElement instance may be used to call other methods on the
-        element, such as click().  If no element is immediately found, the
-        attempt to locate an element will be repeated for up to the amount of
-        time set by set_search_timeout().
+        element, such as click().  If no element is immediately found,
+        the attempt to locate an element will be repeated for up to the
+        amount of time set by set_search_timeout().
 
-        :param method: The method to use to locate the elements; one of:
-                       "id", "name", "class name", "tag name", "css selector", "link text",
-                       "partial link text", "xpath", "anon" and "anon attribute".
-                       Note that the "name", "link text" and
-                       "partial link test" methods are not supported in the chrome dom.
+        :param method: The method to use to locate the elements; one
+            of: "id", "name", "class name", "tag name", "css selector",
+            "link text", "partial link text", "xpath", "anon" and "anon
+            attribute". Note that the "name", "link text" and "partial link
+            test" methods are not supported in the chrome DOM.
         :param target: The target of the search.  For example, if method =
-                       "tag", target might equal "div".  If method = "id", target would be
-                       an element id.
+            "tag", target might equal "div".  If method = "id", target would be
+            an element id.
         :param id: If specified, search for elements only inside the element
-                   with the specified id.
-        '''
-        kwargs = { 'value': target, 'using': method }
+            with the specified id.
+        """
+        body = {"value": target, "using": method}
         if id:
-            kwargs['element'] = id
-        response = self._send_message('findElements', 'value', **kwargs)
-        assert(isinstance(response, list))
-        elements = []
-        for x in response:
-            elements.append(HTMLElement(self, x['ELEMENT']))
-        return elements
+            body["element"] = id
+        els = self._send_message(
+            "findElements", body, key="value" if self.protocol == 1 else None)
+        assert(isinstance(els, list))
+        rv = []
+        for el in els:
+            rv.append(HTMLElement(self, el["ELEMENT"]))
+        return rv
 
     def get_active_element(self):
-        response = self._send_message('getActiveElement', 'value')
-        return HTMLElement(self, response)
+        el = self._send_message("getActiveElement", key="value")
+        return HTMLElement(self, el)
 
     def log(self, msg, level=None):
-        '''
-        Stores a timestamped log message in the Marionette server for later retrieval.
+        """Stores a timestamped log message in the Marionette server
+        for later retrieval.
 
         :param msg: String with message to log.
         :param level: String with log level (e.g. "INFO" or "DEBUG"). If None,
-         defaults to "INFO".
-        '''
-        return self._send_message('log', 'ok', value=msg, level=level)
+            defaults to "INFO".
+        """
+        body = {"value": msg, "level": level}
+        self._send_message("log", body)
 
     def get_logs(self):
-        '''
-        Returns the list of logged messages.
+        """Returns the list of logged messages.
 
         Each log message is an array with three string elements: the level,
         the message, and a date.
 
-        Usage example:
-
-        ::
+        Usage example::
 
-          marionette.log("I AM INFO")
-          marionette.log("I AM ERROR", "ERROR")
-          logs = marionette.get_logs()
-          assert logs[0][1] == "I AM INFO"
-          assert logs[1][1] == "I AM ERROR"
-        '''
-        return self._send_message('getLogs', 'value')
+            marionette.log("I AM INFO")
+            marionette.log("I AM ERROR", "ERROR")
+            logs = marionette.get_logs()
+            assert logs[0][1] == "I AM INFO"
+            assert logs[1][1] == "I AM ERROR"
+        """
+        return self._send_message("getLogs",
+                                  key="value" if self.protocol == 1 else None)
 
     def import_script(self, js_file):
-        '''
-        Imports a script into the scope of the execute_script and execute_async_script calls.
+        """Imports a script into the scope of the execute_script and
+        execute_async_script calls.
 
-        This is particularly useful if you wish to import your own libraries.
+        This is particularly useful if you wish to import your own
+        libraries.
 
         :param js_file: Filename of JavaScript file to import.
 
         For example, Say you have a script, importfunc.js, that contains:
 
         ::
 
-          let testFunc = function() { return "i'm a test function!";};
+            let testFunc = function() { return "i'm a test function!";};
 
-        Assuming this file is in the same directory as the test, you could do
-        something like:
+        Assuming this file is in the same directory as the test, you
+        could do something like:
 
         ::
 
-          js = os.path.abspath(os.path.join(__file__, os.path.pardir, "importfunc.js"))
-          marionette.import_script(js)
-          assert "i'm a test function!" == self.marionette.execute_script("return testFunc();")
-        '''
-        js = ''
-        with open(js_file, 'r') as f:
+            js = os.path.abspath(os.path.join(__file__, os.path.pardir, "importfunc.js"))
+            marionette.import_script(js)
+            assert "i'm a test function!" == self.marionette.execute_script("return testFunc();")
+        """
+        js = ""
+        with open(js_file, "r") as f:
             js = f.read()
-        return self._send_message('importScript', 'ok', script=js)
+        body = {"script": js}
+        self._send_message("importScript", body)
 
     def clear_imported_scripts(self):
-        '''
-        Clears all imported scripts in this context, ie: calling clear_imported_scripts in chrome
-        context will clear only scripts you imported in chrome, and will leave the scripts
-        you imported in content context.
-        '''
-        return self._send_message('clearImportedScripts', 'ok')
+        """Clears all imported scripts in this context, ie: calling
+        clear_imported_scripts in chrome context will clear only scripts
+        you imported in chrome, and will leave the scripts you imported
+        in content context.
+        """
+        self._send_message("clearImportedScripts")
 
     def add_cookie(self, cookie):
-        """
-        Adds a cookie to your current session.
+        """Adds a cookie to your current session.
 
-        :param cookie: A dictionary object, with required keys - "name" and
-         "value"; optional keys - "path", "domain", "secure", "expiry".
+        :param cookie: A dictionary object, with required keys - "name"
+            and "value"; optional keys - "path", "domain", "secure",
+            "expiry".
 
         Usage example:
 
         ::
 
-          driver.add_cookie({'name': 'foo', 'value': 'bar'})
-          driver.add_cookie({'name': 'foo', 'value': 'bar', 'path': '/'})
-          driver.add_cookie({'name': 'foo', 'value': 'bar', 'path': '/',
-                             'secure': True})
+            driver.add_cookie({"name": "foo", "value": "bar"})
+            driver.add_cookie({"name": "foo", "value": "bar", "path": "/"})
+            driver.add_cookie({"name": "foo", "value": "bar", "path": "/",
+                               "secure": True})
         """
-        return self._send_message('addCookie', 'ok', cookie=cookie)
+        body = {"cookie": cookie}
+        self._send_message("addCookie", body)
 
     def delete_all_cookies(self):
-        """
-        Delete all cookies in the scope of the current session.
+        """Delete all cookies in the scope of the current session.
 
         Usage example:
 
         ::
 
-          driver.delete_all_cookies()
+            driver.delete_all_cookies()
         """
-        return self._send_message('deleteAllCookies', 'ok')
+        self._send_message("deleteAllCookies")
 
     def delete_cookie(self, name):
-        """
-        Delete a cookie by its name.
+        """Delete a cookie by its name.
 
         :param name: Name of cookie to delete.
 
         Usage example:
 
         ::
 
-          driver.delete_cookie('foo')
+            driver.delete_cookie("foo")
         """
-        return self._send_message('deleteCookie', 'ok', name=name);
+        self._send_message("deleteCookie", {"name": name})
 
     def get_cookie(self, name):
-        """
-        Get a single cookie by name. Returns the cookie if found, None if not.
+        """Get a single cookie by name. Returns the cookie if found,
+        None if not.
 
         :param name: Name of cookie to get.
         """
         cookies = self.get_cookies()
         for cookie in cookies:
-            if cookie['name'] == name:
+            if cookie["name"] == name:
                 return cookie
         return None
 
     def get_cookies(self):
         """Get all the cookies for the current domain.
 
         This is the equivalent of calling `document.cookie` and
         parsing the result.
 
-        :returns: A set of cookies for the current domain.
-
+        :returns: A list of cookies for the current domain.
         """
-
-        return self._send_message("getCookies", "value")
+        return self._send_message("getCookies", key="value" if self.protocol == 1 else None)
 
     @property
     def application_cache(self):
         return ApplicationCache(self)
 
     def screenshot(self, element=None, highlights=None, format="base64",
                    full=True):
         """Takes a screenshot of a web element or the current frame.
@@ -1796,83 +1775,85 @@ class Marionette(object):
             when `element` is None.
         """
 
         if element:
             element = element.id
         lights = None
         if highlights:
             lights = [highlight.id for highlight in highlights]
-        screenshot_data = self._send_message("takeScreenshot", "value",
-                                             id=element, highlights=lights,
-                                             full=full)
-        if format == 'base64':
-            return screenshot_data
-        elif format == 'binary':
-            return base64.b64decode(screenshot_data.encode('ascii'))
+
+        body = {"id": element,
+                "highlights": lights,
+                "full": full}
+        data = self._send_message("takeScreenshot", body, key="value")
+
+        if format == "base64":
+            return data
+        elif format == "binary":
+            return base64.b64decode(data.encode("ascii"))
         else:
             raise ValueError("format parameter must be either 'base64'"
                              " or 'binary', not {0}".format(repr(format)))
 
     @property
     def orientation(self):
         """Get the current browser orientation.
 
         Will return one of the valid primary orientation values
         portrait-primary, landscape-primary, portrait-secondary, or
         landscape-secondary.
-
         """
-        return self._send_message("getScreenOrientation", "value")
+        return self._send_message("getScreenOrientation", key="value")
 
     def set_orientation(self, orientation):
         """Set the current browser orientation.
 
         The supplied orientation should be given as one of the valid
         orientation values.  If the orientation is unknown, an error
         will be raised.
 
         Valid orientations are "portrait" and "landscape", which fall
         back to "portrait-primary" and "landscape-primary"
         respectively, and "portrait-secondary" as well as
         "landscape-secondary".
 
         :param orientation: The orientation to lock the screen in.
-
         """
-        self._send_message("setScreenOrientation", "ok", orientation=orientation)
+        body = {"orientation": orientation}
+        self._send_message("setScreenOrientation", body)
         if self.emulator:
             self.emulator.screen.orientation = orientation.lower()
 
     @property
     def window_size(self):
         """Get the current browser window size.
 
         Will return the current browser window size in pixels. Refers to
         window outerWidth and outerHeight values, which include scroll bars,
         title bars, etc.
 
         :returns: dictionary representation of current window width and height
-
         """
-        return self._send_message("getWindowSize", "value")
+        return self._send_message("getWindowSize",
+                                  key="value" if self.protocol == 1 else None)
 
     def set_window_size(self, width, height):
         """Resize the browser window currently in focus.
 
         The supplied width and height values refer to the window outerWidth
         and outerHeight values, which include scroll bars, title bars, etc.
 
         An error will be returned if the requested window size would result
         in the window being in the maximised state.
 
         :param width: The width to resize the window to.
         :param height: The height to resize the window to.
 
         """
-        self._send_message("setWindowSize", "ok", width=width, height=height)
+        body = {"width": width, "height": height}
+        self._send_message("setWindowSize", body)
 
     def maximize_window(self):
         """ Resize the browser window currently receiving commands. The action
         should be equivalent to the user pressing the the maximize button
         """
-
-        return self._send_message("maximizeWindow", "ok")
+        return self._send_message("maximizeWindow")
--- a/testing/marionette/transport/marionette_transport/transport.py
+++ b/testing/marionette/transport/marionette_transport/transport.py
@@ -5,51 +5,50 @@
 import datetime
 import errno
 import json
 import socket
 import time
 
 
 class MarionetteTransport(object):
-    """ The Marionette socket client.  This speaks the same protocol
-        as the remote debugger inside Gecko, in which messages are
-        always preceded by the message length and a colon, e.g.,
+    """The Marionette socket client.  This speaks the same protocol
+    as the remote debugger inside Gecko, in which messages are always
+    preceded by the message length and a colon, e.g.:
 
-        20:{'command': 'test'}
+        20:{"command": "test"}
     """
 
     max_packet_length = 4096
     connection_lost_msg = "Connection to Marionette server is lost. Check gecko.log (desktop firefox) or logcat (b2g) for errors."
 
     def __init__(self, addr, port, socket_timeout=360.0):
         self.addr = addr
         self.port = port
         self.socket_timeout = socket_timeout
         self.sock = None
-        self.traits = None
-        self.applicationType = None
-        self.actor = 'root'
+        self.protocol = 1
+        self.application_type = None
 
     def _recv_n_bytes(self, n):
-        """ Convenience method for receiving exactly n bytes from
-            self.sock (assuming it's open and connected).
+        """Convenience method for receiving exactly n bytes from self.sock
+        (assuming it's open and connected).
         """
-        data = ''
+        data = ""
         while len(data) < n:
             chunk = self.sock.recv(n - len(data))
-            if chunk == '':
+            if chunk == "":
                 break
             data += chunk
         return data
 
     def receive(self):
-        """ Receive the next complete response from the server, and return
-            it as a dict.  Each response from the server is prepended by
-            len(message) + ':'.
+        """Receive the next complete response from the server, and
+        return it as a JSON structure.  Each response from the server
+        is prepended by len(message) + ":".
         """
         assert(self.sock)
         now = time.time()
         response = ''
         bytes_to_recv = 10
         while time.time() - now < self.socket_timeout:
             try:
                 data = self.sock.recv(bytes_to_recv)
@@ -64,63 +63,58 @@ class MarionetteTransport(object):
                 length = response[0:sep]
                 remaining = response[sep + 1:]
                 if len(remaining) == int(length):
                     return json.loads(remaining)
                 bytes_to_recv = int(length) - len(remaining)
         raise socket.timeout('connection timed out after %d s' % self.socket_timeout)
 
     def connect(self):
-        """ Connect to the server and process the hello message we expect
-            to receive in response.
+        """Connect to the server and process the hello message we expect
+        to receive in response.
+
+        Return a tuple of the protocol level and the application type.
         """
         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self.sock.settimeout(self.socket_timeout)
         try:
             self.sock.connect((self.addr, self.port))
         except:
             # Unset self.sock so that the next attempt to send will cause
             # another connection attempt.
             self.sock = None
             raise
         self.sock.settimeout(2.0)
+
         hello = self.receive()
-        self.traits = hello.get('traits')
-        self.applicationType = hello.get('applicationType')
+        self.protocol = hello.get("marionetteProtocol", 1)
+        self.application_type = hello.get("applicationType")
 
-        # get the marionette actor id
-        response = self.send({'to': 'root', 'name': 'getMarionetteID'})
-        self.actor = response['id']
+        return (self.protocol, self.application_type)
 
-    def send(self, msg):
-        """ Send a message on the socket, prepending it with len(msg) + ':'.
-        """
+    def send(self, data):
+        """Send a message on the socket, prepending it with len(msg) + ":"."""
         if not self.sock:
             self.connect()
-        if 'to' not in msg:
-            msg['to'] = self.actor
-        data = json.dumps(msg)
-        data = '%s:%s' % (len(data), data)
+        data = "%s:%s" % (len(data), data)
 
         for packet in [data[i:i + self.max_packet_length] for i in
                        range(0, len(data), self.max_packet_length)]:
-            try: 
+            try:
                 self.sock.send(packet)
             except IOError as e:
                 if e.errno == errno.EPIPE:
                     raise IOError("%s: %s" % (str(e), self.connection_lost_msg))
                 else:
                     raise e
 
-        response = self.receive()
-        return response
+        return self.receive()
 
     def close(self):
-        """ Close the socket.
-        """
+        """Close the socket."""
         if self.sock:
             self.sock.close()
         self.sock = None
 
     @staticmethod
     def wait_for_port(host, port, timeout=60):
         """ Wait for the specified Marionette host/port to be available."""
         starttime = datetime.datetime.now()
--- a/testing/mozbase/mozlog/mozlog/formatters/__init__.py
+++ b/testing/mozbase/mozlog/mozlog/formatters/__init__.py
@@ -1,14 +1,19 @@
 # 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 json
 from unittest import UnittestFormatter
 from xunit import XUnitFormatter
 from html import HTMLFormatter
 from machformatter import MachFormatter
 from tbplformatter import TbplFormatter
 from errorsummary import ErrorSummaryFormatter
 
+try:
+    import ujson as json
+except ImportError:
+    import json
+
+
 def JSONFormatter():
     return lambda x: json.dumps(x) + "\n"
--- a/testing/mozharness/configs/unittests/linux_unittest.py
+++ b/testing/mozharness/configs/unittests/linux_unittest.py
@@ -104,17 +104,20 @@ config = {
                           'options': ['--setpref=browser.tabs.remote=true',
                                       '--setpref=browser.tabs.remote.autostart=true',
                                       '--setpref=layers.offmainthreadcomposition.testing.enabled=true',
                                       '--setpref=layers.async-pan-zoom.enabled=true',
                                       'tests/reftest/tests/testing/crashtest/crashtests.list']},
     },
     "all_xpcshell_suites": {
         "xpcshell": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
-                     "%(abs_app_dir)s/" + XPCSHELL_NAME]
+                     "%(abs_app_dir)s/" + XPCSHELL_NAME],
+        "xpcshell-addons": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
+                            "--tag=addons",
+                            "%(abs_app_dir)s/" + XPCSHELL_NAME]
     },
     "all_cppunittest_suites": {
         "cppunittest": ['tests/cppunittest']
     },
     "all_jittest_suites": {
         "jittest": [],
         "jittest1": ["--total-chunks=2", "--this-chunk=1"],
         "jittest2": ["--total-chunks=2", "--this-chunk=2"],
--- a/testing/mozharness/configs/unittests/mac_unittest.py
+++ b/testing/mozharness/configs/unittests/mac_unittest.py
@@ -85,17 +85,20 @@ config = {
                         'tests/reftest/tests/layout/reftests/reftest-sanity/reftest.list'],
         "crashtest-ipc": ['--setpref=browser.tabs.remote=true',
                           '--setpref=browser.tabs.remote.autostart=true',
                           '--setpref=layers.async-pan-zoom.enabled=true',
                           'tests/reftest/tests/testing/crashtest/crashtests.list'],
     },
     "all_xpcshell_suites": {
         "xpcshell": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
-                     "%(abs_app_dir)s/" + XPCSHELL_NAME]
+                     "%(abs_app_dir)s/" + XPCSHELL_NAME],
+        "xpcshell-addons": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
+                            "--tag=addons",
+                            "%(abs_app_dir)s/" + XPCSHELL_NAME]
     },
     "all_cppunittest_suites": {
         "cppunittest": ['tests/cppunittest']
     },
     "all_jittest_suites": {
         "jittest": []
     },
     "all_mozbase_suites": {
--- a/testing/mozharness/configs/unittests/win_unittest.py
+++ b/testing/mozharness/configs/unittests/win_unittest.py
@@ -100,17 +100,20 @@ config = {
                          "tests/reftest/tests/layout/reftests/reftest.list"],
         "crashtest-ipc": ['--setpref=browser.tabs.remote=true',
                           '--setpref=browser.tabs.remote.autostart=true',
                           '--setpref=layers.async-pan-zoom.enabled=true',
                           'tests/reftest/tests/testing/crashtest/crashtests.list'],
     },
     "all_xpcshell_suites": {
         "xpcshell": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
-                     "%(abs_app_dir)s/" + XPCSHELL_NAME]
+                     "%(abs_app_dir)s/" + XPCSHELL_NAME],
+        "xpcshell-addons": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
+                            "--tag=addons",
+                            "%(abs_app_dir)s/" + XPCSHELL_NAME]
     },
     "all_cppunittest_suites": {
         "cppunittest": ['tests/cppunittest']
     },
     "all_jittest_suites": {
         "jittest": []
     },
     "all_mozbase_suites": {
--- a/testing/mozharness/mozharness/mozilla/building/buildbase.py
+++ b/testing/mozharness/mozharness/mozilla/building/buildbase.py
@@ -1400,20 +1400,21 @@ or run without that action (ie: --no-{ac
                 templates = contents['nightly']
 
                 # Nightly builds with l10n counterparts also publish to the
                 # 'en-US' locale.
                 if self.config.get('publish_nightly_en_US_routes'):
                     templates.extend(contents['l10n'])
             else:
                 templates = contents['routes']
+        index = self.config.get('taskcluster_index', 'index.garbage.staging')
         routes = []
         for template in templates:
             fmt = {
-                'index': 'index.garbage.staging.mshal-testing', # TODO
+                'index': index,
                 'project': self.buildbot_config['properties']['branch'],
                 'head_rev': self.query_revision(),
                 'build_product': self.config['stage_product'],
                 'build_name': self.query_build_name(),
                 'build_type': self.query_build_type(),
                 'locale': 'en-US',
             }
             fmt.update(self.buildid_to_dict(self.query_buildid()))
@@ -1422,17 +1423,16 @@ or run without that action (ie: --no-{ac
 
         tc = Taskcluster(self.branch,
                          self.query_pushdate(), # Use pushdate as the rank
                          client_id,
                          access_token,
                          self.log_obj,
                          )
 
-        index = self.config.get('taskcluster_index', 'index.garbage.staging')
         # TODO: Bug 1165980 - these should be in tree
         routes.extend([
             "%s.buildbot.branches.%s.%s" % (index, self.branch, self.stage_platform),
             "%s.buildbot.revisions.%s.%s.%s" % (index, self.query_revision(), self.branch, self.stage_platform),
         ])
         task = tc.create_task(routes)
         tc.claim_task(task)
 
--- a/testing/mozharness/scripts/desktop_l10n.py
+++ b/testing/mozharness/scripts/desktop_l10n.py
@@ -1023,19 +1023,17 @@ class DesktopSingleLocale(LocalesMixin, 
             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 = {
-                    # TODO: Bug 1133074
-                    #index = self.config.get('taskcluster_index', 'index.garbage.staging')
-                    'index': 'index.garbage.staging.mshal-testing',
+                    'index': self.config.get('taskcluster_index', 'index.garbage.staging'),
                     'project': branch,
                     'head_rev': revision,
                     '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()))
--- a/testing/taskcluster/mach_commands.py
+++ b/testing/taskcluster/mach_commands.py
@@ -282,17 +282,17 @@ class Graph(object):
 
         jobs = templates.load(job_path, {})
 
         job_graph = parse_commit(message, jobs)
         mozharness = load_mozharness_info()
 
         # Template parameters used when expanding the graph
         parameters = dict(gaia_info().items() + {
-            'index': 'index.garbage.staging.mshal-testing', #TODO
+            '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'],
--- a/testing/web-platform/meta/media-source/mediasource-is-type-supported.html.ini
+++ b/testing/web-platform/meta/media-source/mediasource-is-type-supported.html.ini
@@ -1,33 +1,41 @@
 [mediasource-is-type-supported.html]
   type: testharness
   prefs: [media.mediasource.enabled:true]
   [Test invalid MIME format "video/webm"]
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
     expected: FAIL
 
   [Test invalid MIME format "video/webm;"]
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
     expected: FAIL
 
   [Test invalid MIME format "video/webm;codecs"]
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
     expected: FAIL
 
   [Test invalid MIME format "video/webm;codecs="]
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
     expected: FAIL
 
   [Test invalid MIME format "video/webm;codecs=""]
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
     expected: FAIL
 
   [Test invalid MIME format "video/webm;codecs="""]
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
     expected: FAIL
 
   [Test valid WebM type "AUDIO/WEBM;CODECS="vorbis""]
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
     expected: FAIL
 
   [Test invalid mismatch between major type and codec ID "audio/webm;codecs="vp8""]
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1191833
     expected: FAIL
 
   [Test valid MP4 type "audio/mp4;codecs="mp4a.67""]
     expected: FAIL
 
   [Test valid MP4 type "video/mp4;codecs="avc1.4d001e""]
     expected:
       if os == "linux": FAIL
--- a/toolkit/components/perfmonitoring/moz.build
+++ b/toolkit/components/perfmonitoring/moz.build
@@ -1,15 +1,14 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 
 FAIL_ON_WARNINGS = True
 
 XPIDL_MODULE = 'toolkit_perfmonitoring'
 
 EXTRA_JS_MODULES += [
     'AddonWatcher.jsm',
--- a/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
+++ b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
@@ -16,16 +16,20 @@
 #include "jsapi.h"
 #include "nsJSUtils.h"
 #include "xpcpublic.h"
 #include "jspubtd.h"
 
 #include "nsIDOMWindow.h"
 #include "nsGlobalWindow.h"
 
+#include "mozilla/unused.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+
 #if defined(XP_WIN)
 #include "windows.h"
 #else
 #include <unistd.h>
 #endif
 
 class nsPerformanceStats: public nsIPerformanceStats {
 public:
@@ -435,25 +439,31 @@ NS_IMETHODIMP nsPerformanceSnapshot::Get
 
 NS_IMETHODIMP nsPerformanceSnapshot::GetProcessData(nsIPerformanceStats * *aProcess)
 {
   NS_IF_ADDREF(*aProcess = mProcessData);
   return NS_OK;
 }
 
 
-NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService)
+NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService, nsIObserver)
 
 nsPerformanceStatsService::nsPerformanceStatsService()
 #if defined(XP_WIN)
   : mProcessId(GetCurrentProcessId())
 #else
   : mProcessId(getpid())
 #endif
+  , mProcessStayed(0)
+  , mProcessMoved(0)
 {
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    mozilla::unused << obs->AddObserver(this, "profile-before-shutdown", false);
+  }
 }
 
 nsPerformanceStatsService::~nsPerformanceStatsService()
 {
 }
 
 //[implicit_jscontext] attribute bool isMonitoringCPOW;
 NS_IMETHODIMP nsPerformanceStatsService::GetIsMonitoringCPOW(JSContext* cx, bool *aIsStopwatchActive)
@@ -502,13 +512,33 @@ NS_IMETHODIMP nsPerformanceStatsService:
 NS_IMETHODIMP nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot)
 {
   nsRefPtr<nsPerformanceSnapshot> snapshot = new nsPerformanceSnapshot();
   nsresult rv = snapshot->Init(cx, mProcessId);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
+  js::GetPerfMonitoringTestCpuRescheduling(JS_GetRuntime(cx), &mProcessStayed, &mProcessMoved);
   snapshot.forget(aSnapshot);
+
   return NS_OK;
 }
 
 
+/* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */
+NS_IMETHODIMP nsPerformanceStatsService::Observe(nsISupports *, const char *, const char16_t *)
+{
+  // Upload telemetry
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    mozilla::unused << obs->RemoveObserver(this, "profile-before-shutdown");
+  }
+
+  if (mProcessStayed + mProcessMoved == 0) {
+    // Nothing to report.
+    return NS_OK;
+  }
+  const uint32_t proportion = ( 100 * mProcessStayed ) / ( mProcessStayed + mProcessMoved );
+  mozilla::Telemetry::Accumulate("PERF_MONITORING_TEST_CPU_RESCHEDULING_PROPORTION_MOVED", proportion);
+
+  return NS_OK;
+}
--- a/toolkit/components/perfmonitoring/nsPerformanceStats.h
+++ b/toolkit/components/perfmonitoring/nsPerformanceStats.h
@@ -1,26 +1,31 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsPerformanceStats_h
 #define nsPerformanceStats_h
 
+#include "nsIObserver.h"
+
 #include "nsIPerformanceStats.h"
 
-class nsPerformanceStatsService : public nsIPerformanceStatsService
+class nsPerformanceStatsService : public nsIPerformanceStatsService, nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPERFORMANCESTATSSERVICE
+  NS_DECL_NSIOBSERVER
 
   nsPerformanceStatsService();
 
 private:
   virtual ~nsPerformanceStatsService();
 
   const uint64_t mProcessId;
+  uint64_t mProcessStayed;
+  uint64_t mProcessMoved;
 protected:
 };
 
 #endif
deleted file mode 100644
--- a/toolkit/components/perfmonitoring/tests/xpcshell/test_compartments.js
+++ /dev/null
@@ -1,169 +0,0 @@
-"use strict";
-
-const {utils: Cu, interfaces: Ci, classes: Cc} = Components;
-
-Cu.import("resource://gre/modules/Task.jsm", this);
-Cu.import("resource://gre/modules/Services.jsm", this);
-Cu.import("resource://gre/modules/PerformanceStats.jsm", this);
-
-function run_test() {
-  run_next_test();
-}
-
-let promiseStatistics = Task.async(function*(name) {
-  yield Promise.resolve(); // Make sure that we wait until
-  // statistics have been updated.
-  let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].
-    getService(Ci.nsIPerformanceStatsService);
-  let snapshot = service.getSnapshot();
-  let componentsData = [];
-  let componentsEnum = snapshot.getComponentsData().enumerate();
-  while (componentsEnum.hasMoreElements()) {
-    let data = componentsEnum.getNext().QueryInterface(Ci.nsIPerformanceStats);
-    let normalized = JSON.parse(JSON.stringify(data));
-    componentsData.push(data);
-  }
-  return {
-    processData: JSON.parse(JSON.stringify(snapshot.getProcessData())),
-    componentsData
-  };
-});
-
-let promiseSetMonitoring = Task.async(function*(to) {
-  let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].
-    getService(Ci.nsIPerformanceStatsService);
-  service.isMonitoringJank = to;
-  service.isMonitoringCPOW = to;
-  yield Promise.resolve();
-});
-
-let promiseSetPerCompartment = Task.async(function*(to) {
-  let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].
-    getService(Ci.nsIPerformanceStatsService);
-  service.isMonitoringPerCompartment = to;
-  yield Promise.resolve();
-});
-
-function getBuiltinStatistics(name, snapshot) {
-  let stats = snapshot.componentsData.find(stats =>
-    stats.isSystem && !stats.addonId
-  );
-  do_print(`Built-in statistics for ${name} were ${stats?"":"not "}found`);
-  do_print(JSON.stringify(snapshot.componentsData, null, "\t"));
-  return stats;
-}
-
-function burnCPU(ms) {
-  do_print("Burning CPU");
-  let counter = 0;
-  let ignored = [];
-  let start = Date.now();
-  while (Date.now() - start < ms) {
-    ignored.push(0);
-    ignored.shift();
-    ++counter;
-  }
-  do_print("Burning CPU over, after " + counter + " iterations");
-}
-
-function ensureEquals(snap1, snap2, name) {
-  for (let k of Object.keys(snap1.processData)) {
-    if (k == "ticks") {
-      // Ticks monitoring cannot be deactivated
-      continue;
-    }
-    Assert.equal(snap1.processData[k], snap2.processData[k], `Same process data value ${k} (${name})`)
-  }
-  let stats1 = snap1.componentsData.sort((a, b) => a.name <= b.name);
-  let stats2 = snap2.componentsData.sort((a, b) => a.name <= b.name);
-  Assert.equal(stats1.length, stats2.length, `Same number of components (${name})`);
-  for (let i = 0; i < stats1.length; ++i) {
-    for (let k of Object.keys(stats1[i])) {
-      if (k == "ticks") {
-        // Ticks monitoring cannot be deactivated
-        continue;
-      }
-      Assert.equal(stats1[i][k], stats1[i][k], `Same component data value ${i} ${k} (${name})`)
-    }
-  }
-}
-
-function hasLowPrecision() {
-  let [sysName, sysVersion] = [Services.sysinfo.getPropertyAsAString("name"), Services.sysinfo.getPropertyAsDouble("version")];
-  do_print(`Running ${sysName} version ${sysVersion}`);
-
-  if (sysName == "Windows_NT" && sysVersion < 6) {
-    do_print("Running old Windows, need to deactivate tests due to bad precision.");
-    return true;
-  }
-  if (sysName == "Linux" && sysVersion <= 2.6) {
-    do_print("Running old Linux, need to deactivate tests due to bad precision.");
-    return true;
-  }
-  do_print("This platform has good precision.")
-  return false;
-}
-
-add_task(function* test_measure() {
-  let skipPrecisionTests = hasLowPrecision();
-  yield promiseSetPerCompartment(false);
-
-  do_print("Burn CPU without the stopwatch");
-  yield promiseSetMonitoring(false);
-  let stats0 = yield promiseStatistics("Initial state");
-  burnCPU(300);
-  let stats1 = yield promiseStatistics("Initial state + burn, without stopwatch");
-
-  do_print("Burn CPU with the stopwatch");
-  yield promiseSetMonitoring(true);
-  burnCPU(300);
-  let stats2 = yield promiseStatistics("Second burn, with stopwatch");
-
-  do_print("Burn CPU without the stopwatch again")
-  yield promiseSetMonitoring(false);
-  let stats3 = yield promiseStatistics("Before third burn, without stopwatch");
-  burnCPU(300);
-  let stats4 = yield promiseStatistics("After third burn, without stopwatch");
-
-  ensureEquals(stats0, stats1, "Initial state vs. Initial state + burn, without stopwatch");
-  let process1 = stats1.processData;
-  let process2 = stats2.processData;
-  let process3 = stats3.processData;
-  let process4 = stats4.processData;
-  if (skipPrecisionTests) {
-    do_print("Skipping totalUserTime check under Windows XP, as timer is not always updated by the OS.")
-  } else {
-    Assert.ok(process2.totalUserTime - process1.totalUserTime >= 10000, `At least 10ms counted for process time (${process2.totalUserTime - process1.totalUserTime})`);
-  }
-  Assert.equal(process2.totalCPOWTime, process1.totalCPOWTime, "We haven't used any CPOW time during the first burn");
-  Assert.equal(process4.totalUserTime, process3.totalUserTime, "After deactivating the stopwatch, we didn't count any time");
-  Assert.equal(process4.totalCPOWTime, process3.totalCPOWTime, "After deactivating the stopwatch, we didn't count any CPOW time");
-
-  let builtin1 = getBuiltinStatistics("Built-ins 1", stats1) || { totalUserTime: 0, totalCPOWTime: 0 };
-  let builtin2 = getBuiltinStatistics("Built-ins 2", stats2);
-  let builtin3 = getBuiltinStatistics("Built-ins 3", stats3);
-  let builtin4 = getBuiltinStatistics("Built-ins 4", stats4);
-  Assert.notEqual(builtin2, null, "Found the statistics for built-ins 2");
-  Assert.notEqual(builtin3, null, "Found the statistics for built-ins 3");
-  Assert.notEqual(builtin4, null, "Found the statistics for built-ins 4");
-
-  if (skipPrecisionTests) {
-    do_print("Skipping totalUserTime check under Windows XP, as timer is not always updated by the OS.")
-  } else {
-    Assert.ok(builtin2.totalUserTime - builtin1.totalUserTime >= 10000, `At least 10ms counted for built-in statistics (${builtin2.totalUserTime - builtin1.totalUserTime})`);
-  }
-  Assert.equal(builtin2.totalCPOWTime, builtin1.totalCPOWTime, "We haven't used any CPOW time during the first burn for the built-in");
-  Assert.equal(builtin2.totalCPOWTime, builtin1.totalCPOWTime, "No CPOW for built-in statistics");
-  Assert.equal(builtin4.totalUserTime, builtin3.totalUserTime, "After deactivating the stopwatch, we didn't count any time for the built-in");
-  Assert.equal(builtin4.totalCPOWTime, builtin3.totalCPOWTime, "After deactivating the stopwatch, we didn't count any CPOW time for the built-in");
-
-  // Ideally, we should be able to look for test_compartments.js, but
-  // it doesn't have its own compartment.
-  for (let stats of [stats1, stats2, stats3, stats4]) {
-    Assert.ok(!stats.componentsData.find(x => x.name.includes("Task.jsm")), "At this stage, Task.jsm doesn't show up in the components data");
-  }
-  yield promiseSetPerCompartment(true);
-  burnCPU(300);
-  let stats5 = yield promiseStatistics("With per-compartment monitoring");
-  Assert.ok(stats5.componentsData.find(x => x.name.includes("Task.jsm")), "With per-compartment monitoring, test_compartments.js shows up");
-});
deleted file mode 100644
--- a/toolkit/components/perfmonitoring/tests/xpcshell/xpcshell.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[DEFAULT]
-head=
-tail=
-
-[test_compartments.js]
-skip-if = toolkit == 'gonk' # Fails on b2g emulator, bug 1147664
--- a/toolkit/components/search/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/search/tests/xpcshell/xpcshell.ini
@@ -58,23 +58,27 @@ support-files =
 [test_addEngine_callback.js]
 [test_multipleIcons.js]
 [test_resultDomain.js]
 [test_serialize_file.js]
 [test_searchSuggest.js]
 [test_async.js]
 [test_async_app.js]
 [test_async_addon.js]
+tags = addons
 [test_async_addon_no_override.js]
+tags = addons
 [test_async_distribution.js]
 [test_async_profile_engine.js]
 [test_sync.js]
 [test_sync_app.js]
 [test_sync_addon.js]
+tags = addons
 [test_sync_addon_no_override.js]
+tags = addons
 [test_sync_distribution.js]
 [test_sync_fallback.js]
 [test_sync_delay_fallback.js]
 [test_sync_profile_engine.js]
 [test_rel_searchform.js]
 [test_selectedEngine.js]
 [test_geodefaults.js]
 [test_hidden.js]
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -8929,10 +8929,18 @@
     "description": "The number of times AddIceCandidate failed on a given PeerConnection, given that ICE succeeded."
   },
   "LOOP_ICE_ADD_CANDIDATE_ERRORS_GIVEN_FAILURE": {
     "expires_in_version": "never",
     "kind": "linear",
     "high": "30",
     "n_buckets": "29",
     "description": "The number of times AddIceCandidate failed on a given PeerConnection, given that ICE failed."
+  },
+  "PERF_MONITORING_TEST_CPU_RESCHEDULING_PROPORTION_MOVED": {
+    "alert_emails": ["dteller@mozilla.com"],
+    "expires_in_version": "44",
+    "kind": "linear",
+    "high": "100",
+    "n_buckets": "20",
+    "description": "Proportion (%) of reschedulings of the main process to another CPU during the execution of code inside a JS compartment. Updated while we are measuring jank."
   }
 }
--- a/toolkit/components/telemetry/tests/unit/xpcshell.ini
+++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini
@@ -18,39 +18,47 @@ generated-files =
   experiment.xpi
   extension.xpi
   extension-2.xpi
   restartless.xpi
   theme.xpi
 
 [test_nsITelemetry.js]
 [test_SubsessionChaining.js]
+tags = addons
 [test_TelemetryEnvironment.js]
 # Bug 1144395: crash on Android 4.3
 skip-if = android_version == "18"
+tags = addons
 [test_PingAPI.js]
 skip-if = os == "android"
 [test_TelemetryFlagClear.js]
 [test_TelemetryLateWrites.js]
 [test_TelemetryLockCount.js]
 [test_TelemetryLog.js]
 [test_TelemetryController.js]
 # Bug 676989: test fails consistently on Android
 # fail-if = os == "android"
 # Bug 1144395: crash on Android 4.3
 skip-if = android_version == "18"
+tags = addons
 [test_TelemetryController_idle.js]
 [test_TelemetryControllerShutdown.js]
+tags = addons
 [test_TelemetryStopwatch.js]
 [test_TelemetryControllerBuildID.js]
 # Bug 1144395: crash on Android 4.3
 skip-if = android_version == "18"
 [test_TelemetrySendOldPings.js]
 skip-if = os == "android" # Disabled due to intermittent orange on Android
+tags = addons
 [test_TelemetrySession.js]
 # Bug 1144395: crash on Android 4.3
 skip-if = android_version == "18"
+tags = addons
 [test_ThreadHangStats.js]
 run-sequentially = Bug 1046307, test can fail intermittently when CPU load is high
 [test_TelemetrySend.js]
 [test_ChildHistograms.js]
 skip-if = os == "android"
+tags = addons
 [test_TelemetryReportingPolicy.js]
+tags = addons
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -1,11 +1,12 @@
 # The file is shared between the two main xpcshell manifest files.
 [DEFAULT]
 skip-if = toolkit == 'android' || toolkit == 'gonk'
+tags = addons
 
 [test_AddonRepository.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_AddonRepository_cache.js]
 # Bug 676992: test consistently hangs on Android
 # Bug 1026805: frequent hangs on OSX 10.8
 skip-if = os == "android" || os == "mac"
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini
@@ -1,8 +1,9 @@
  [DEFAULT]
 head = head_addons.js head_unpack.js
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 dupe-manifest =
+tags = addons
 
 [include:xpcshell-shared.ini]
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 skip-if = buildapp == 'mulet' || toolkit == 'android' || toolkit == 'gonk'
+tags = addons
 head = head_addons.js
 tail =
 firefox-appdir = browser
 dupe-manifest =
 support-files =
   data/**
   xpcshell-shared.ini
 
--- a/widget/gtk/nsDeviceContextSpecG.cpp
+++ b/widget/gtk/nsDeviceContextSpecG.cpp
@@ -152,28 +152,16 @@ NS_IMETHODIMP nsDeviceContextSpecGTK::Ge
   gfxSize surfaceSize(width, height);
 
   // Determine the real format with some GTK magic
   if (format == nsIPrintSettings::kOutputFormatNative) {
     if (mIsPPreview) {
       // There is nothing to detect on Print Preview, use PS.
       format = nsIPrintSettings::kOutputFormatPS;
     } else {
-      const gchar* fmtGTK = gtk_print_settings_get(mGtkPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT);
-      if (fmtGTK) {
-        if (nsDependentCString(fmtGTK).EqualsIgnoreCase("pdf")) {
-          format = nsIPrintSettings::kOutputFormatPDF;
-        } else {
-          format = nsIPrintSettings::kOutputFormatPS;
-        }
-      }
-    }
-
-    // If we haven't found the format at this point, we're sunk. :(
-    if (format == nsIPrintSettings::kOutputFormatNative) {
       return NS_ERROR_FAILURE;
     }
   }
 
   if (format == nsIPrintSettings::kOutputFormatPDF) {
     surface = new gfxPDFSurface(stream, surfaceSize);
   } else {
     int32_t orientation;
--- a/widget/gtk/nsPrintSettingsGTK.cpp
+++ b/widget/gtk/nsPrintSettingsGTK.cpp
@@ -195,27 +195,54 @@ nsPrintSettingsGTK::SetGtkPrintSettings(
  */
 void
 nsPrintSettingsGTK::SetGtkPrinter(GtkPrinter *aPrinter)
 {
   if (mGTKPrinter)
     g_object_unref(mGTKPrinter);
 
   mGTKPrinter = (GtkPrinter*) g_object_ref(aPrinter);
+}
 
-  // Prior to gtk 2.24, gtk_printer_accepts_pdf() and
-  // gtk_printer_accepts_ps() always returned true regardless of the
-  // printer's capability.
-  bool shouldTrustGTK =
-    (gtk_major_version > 2 ||
-     (gtk_major_version == 2 && gtk_minor_version >= 24));
-  bool acceptsPDF = shouldTrustGTK && gtk_printer_accepts_pdf(mGTKPrinter);
+NS_IMETHODIMP nsPrintSettingsGTK::GetOutputFormat(int16_t *aOutputFormat)
+{
+  NS_ENSURE_ARG_POINTER(aOutputFormat);
+
+  int16_t format;
+  nsresult rv = nsPrintSettings::GetOutputFormat(&format);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
-  SetOutputFormat(acceptsPDF ? nsIPrintSettings::kOutputFormatPDF
-                             : nsIPrintSettings::kOutputFormatPS);
+  if (format == nsIPrintSettings::kOutputFormatNative) {
+    const gchar* fmtGTK =
+      gtk_print_settings_get(mPrintSettings,
+                             GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT);
+    if (fmtGTK) {
+      if (nsDependentCString(fmtGTK).EqualsIgnoreCase("pdf")) {
+        format = nsIPrintSettings::kOutputFormatPDF;
+      } else {
+        format = nsIPrintSettings::kOutputFormatPS;
+      }
+    } else if (GTK_IS_PRINTER(mGTKPrinter)) {
+      // Prior to gtk 2.24, gtk_printer_accepts_pdf() and
+      // gtk_printer_accepts_ps() always returned true regardless of the
+      // printer's capability.
+      bool shouldTrustGTK =
+        (gtk_major_version > 2 ||
+         (gtk_major_version == 2 && gtk_minor_version >= 24));
+      bool acceptsPDF = shouldTrustGTK && gtk_printer_accepts_pdf(mGTKPrinter);
+
+      format = acceptsPDF ? nsIPrintSettings::kOutputFormatPDF
+                          : nsIPrintSettings::kOutputFormatPS;
+    }
+  }
+
+  *aOutputFormat = format;
+  return NS_OK;
 }
 
 /**
  * Reimplementation of nsPrintSettings functions so that we get the values
  * from the GTK objects rather than our own variables.
  */
 
 NS_IMETHODIMP nsPrintSettingsGTK::GetPrintRange(int16_t *aPrintRange)
--- a/widget/gtk/nsPrintSettingsGTK.h
+++ b/widget/gtk/nsPrintSettingsGTK.h
@@ -108,16 +108,18 @@ public:
   NS_IMETHOD GetPageRanges(nsTArray<int32_t> &aPages) override;
 
   NS_IMETHOD GetResolution(int32_t *aResolution) override;
   NS_IMETHOD SetResolution(int32_t aResolution) override;
 
   NS_IMETHOD GetDuplex(int32_t *aDuplex) override;
   NS_IMETHOD SetDuplex(int32_t aDuplex) override;
 
+  NS_IMETHOD GetOutputFormat(int16_t *aOutputFormat) override;
+
 protected:
   virtual ~nsPrintSettingsGTK();
 
   nsPrintSettingsGTK(const nsPrintSettingsGTK& src);
   nsPrintSettingsGTK& operator=(const nsPrintSettingsGTK& rhs);
 
   virtual nsresult _Clone(nsIPrintSettings **_retval) override;
   virtual nsresult _Assign(nsIPrintSettings *aPS) override;
--- a/widget/windows/nsLookAndFeel.cpp
+++ b/widget/windows/nsLookAndFeel.cpp
@@ -561,16 +561,18 @@ GetSysFontInfo(HDC aHDC, LookAndFeel::Fo
       break;
     case LookAndFeel::eFont_StatusBar:
     case LookAndFeel::eFont_Tooltips:
       ptrLogFont = &ncm.lfStatusFont;
       break;
     case LookAndFeel::eFont_Widget:
     case LookAndFeel::eFont_Dialog:
     case LookAndFeel::eFont_Button:
+    case LookAndFeel::eFont_Field:
+    case LookAndFeel::eFont_List:
       // XXX It's not clear to me whether this is exactly the right
       // set of LookAndFeel values to map to the dialog font; we may
       // want to add or remove cases here after reviewing the visual
       // results under various Windows versions.
       useShellDlg = true;
       // Fall through so that we can get size from lfMessageFont;
       // but later we'll use the (virtual) "MS Shell Dlg 2" font name
       // instead of the LOGFONT's.
--- a/xpcom/base/nsConsoleService.cpp
+++ b/xpcom/base/nsConsoleService.cpp
@@ -50,83 +50,77 @@ NS_IMPL_QUERY_INTERFACE_CI(nsConsoleServ
 NS_IMPL_CI_INTERFACE_GETTER(nsConsoleService, nsIConsoleService, nsIObserver)
 
 static bool sLoggingEnabled = true;
 static bool sLoggingBuffered = true;
 #if defined(ANDROID)
 static bool sLoggingLogcat = true;
 #endif // defined(ANDROID)
 
+nsConsoleService::MessageElement::~MessageElement()
+{
+}
 
 nsConsoleService::nsConsoleService()
-  : mMessages(nullptr)
-  , mCurrent(0)
-  , mFull(false)
+  : mCurrentSize(0)
   , mDeliveringMessage(false)
   , mLock("nsConsoleService.mLock")
 {
   // XXX grab this from a pref!
   // hm, but worry about circularity, bc we want to be able to report
   // prefs errs...
-  mBufferSize = 250;
+  mMaximumSize = 250;
 }
 
 
 void
 nsConsoleService::ClearMessagesForWindowID(const uint64_t innerID)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
-  // Remove the messages related to this window
-  for (uint32_t i = 0; i < mBufferSize && mMessages[i]; i++) {
-    // Only messages implementing nsIScriptError interface exposes the inner window ID
-    nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(mMessages[i]);
+  for (MessageElement* e = mMessages.getFirst(); e != nullptr; ) {
+    // Only messages implementing nsIScriptError interface expose the
+    // inner window ID.
+    nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(e->Get());
     if (!scriptError) {
+      e = e->getNext();
       continue;
     }
     uint64_t innerWindowID;
     nsresult rv = scriptError->GetInnerWindowID(&innerWindowID);
     if (NS_FAILED(rv) || innerWindowID != innerID) {
+      e = e->getNext();
       continue;
     }
 
-    // Free this matching message!
-    NS_RELEASE(mMessages[i]);
+    MessageElement* next = e->getNext();
+    e->remove();
+    delete e;
+    mCurrentSize--;
+    MOZ_ASSERT(mCurrentSize < mMaximumSize);
 
-    uint32_t j = i;
-    // Now shift all the following messages
-    // XXXkhuey this is not an efficient way to iterate through an array ...
-    for (; j < mBufferSize - 1 && mMessages[j + 1]; j++) {
-      mMessages[j] = mMessages[j + 1];
-    }
-    // Nullify the current slot
-    mMessages[j] = nullptr;
-    mCurrent = j;
+    e = next;
+  }
+}
 
-    // The array is no longer full
-    mFull = false;
-
-    // Ensure the next iteration handles the messages we just shifted down
-    i--;
+void
+nsConsoleService::ClearMessages()
+{
+  while (!mMessages.isEmpty()) {
+    MessageElement* e = mMessages.popFirst();
+    delete e;
   }
+  mCurrentSize = 0;
 }
 
 nsConsoleService::~nsConsoleService()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
-  uint32_t i = 0;
-  while (i < mBufferSize && mMessages[i]) {
-    NS_RELEASE(mMessages[i]);
-    i++;
-  }
-
-  if (mMessages) {
-    free(mMessages);
-  }
+  ClearMessages();
 }
 
 class AddConsolePrefWatchers : public nsRunnable
 {
 public:
   explicit AddConsolePrefWatchers(nsConsoleService* aConsole) : mConsole(aConsole)
   {
   }
@@ -152,25 +146,16 @@ public:
 
 private:
   nsRefPtr<nsConsoleService> mConsole;
 };
 
 nsresult
 nsConsoleService::Init()
 {
-  mMessages = (nsIConsoleMessage**)
-    moz_xmalloc(mBufferSize * sizeof(nsIConsoleMessage*));
-  if (!mMessages) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  // Array elements should be 0 initially for circular buffer algorithm.
-  memset(mMessages, 0, mBufferSize * sizeof(nsIConsoleMessage*));
-
   NS_DispatchToMainThread(new AddConsolePrefWatchers(this));
 
   return NS_OK;
 }
 
 namespace {
 
 class LogMessageRunnable : public nsRunnable
@@ -236,21 +221,17 @@ nsConsoleService::LogMessageWithMode(nsI
     aMessage->ToString(msg);
     NS_WARNING(nsPrintfCString("Reentrancy error: some client attempted "
       "to display a message to the console while in a console listener. "
       "The following message was discarded: \"%s\"", msg.get()).get());
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<LogMessageRunnable> r;
-  nsIConsoleMessage* retiredMessage;
-
-  if (sLoggingBuffered) {
-    NS_ADDREF(aMessage); // early, in case it's same as replaced below.
-  }
+  nsCOMPtr<nsIConsoleMessage> retiredMessage;
 
   /*
    * Lock while updating buffer, and while taking snapshot of
    * listeners array.
    */
   {
     MutexAutoLock lock(mLock);
 
@@ -306,28 +287,26 @@ nsConsoleService::LogMessageWithMode(nsI
       int prefixPos = msg.Find(GetJSLabelPrefix());
       if (prefixPos >= 0) {
         nsDependentCSubstring submsg(msg, prefixPos);
         AddLabel("%s", submsg.BeginReading());
       }
     }
 #endif
 
-    /*
-     * If there's already a message in the slot we're about to replace,
-     * we've wrapped around, and we need to release the old message.  We
-     * save a pointer to it, so we can release below outside the lock.
-     */
-    retiredMessage = mMessages[mCurrent];
-
     if (sLoggingBuffered) {
-      mMessages[mCurrent++] = aMessage;
-      if (mCurrent == mBufferSize) {
-        mCurrent = 0; // wrap around.
-        mFull = true;
+      MessageElement* e = new MessageElement(aMessage);
+      mMessages.insertBack(e);
+      if (mCurrentSize != mMaximumSize) {
+        mCurrentSize++;
+      } else {
+        MessageElement* p = mMessages.popFirst();
+        MOZ_ASSERT(p);
+        retiredMessage = p->forget();
+        delete p;
       }
     }
 
     if (mListeners.Count() > 0) {
       r = new LogMessageRunnable(aMessage, this);
     }
   }
 
@@ -372,65 +351,48 @@ nsConsoleService::LogStringMessage(const
 }
 
 NS_IMETHODIMP
 nsConsoleService::GetMessageArray(uint32_t* aCount,
                                   nsIConsoleMessage*** aMessages)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
-  nsIConsoleMessage** messageArray;
-
-  /*
-   * Lock the whole method, as we don't want anyone mucking with mCurrent or
-   * mFull while we're copying out the buffer.
-   */
   MutexAutoLock lock(mLock);
 
-  if (mCurrent == 0 && !mFull) {
+  if (mMessages.isEmpty()) {
     /*
      * Make a 1-length output array so that nobody gets confused,
      * and return a count of 0.  This should result in a 0-length
      * array object when called from script.
      */
-    messageArray = (nsIConsoleMessage**)
+    nsIConsoleMessage** messageArray = (nsIConsoleMessage**)
       moz_xmalloc(sizeof(nsIConsoleMessage*));
     *messageArray = nullptr;
     *aMessages = messageArray;
     *aCount = 0;
 
     return NS_OK;
   }
 
-  uint32_t resultSize = mFull ? mBufferSize : mCurrent;
-  messageArray =
-    (nsIConsoleMessage**)moz_xmalloc((sizeof(nsIConsoleMessage*))
-                                         * resultSize);
-
-  if (!messageArray) {
-    *aMessages = nullptr;
-    *aCount = 0;
-    return NS_ERROR_FAILURE;
-  }
+  MOZ_ASSERT(mCurrentSize <= mMaximumSize);
+  nsIConsoleMessage** messageArray =
+    static_cast<nsIConsoleMessage**>(moz_xmalloc(sizeof(nsIConsoleMessage*)
+                                                 * mCurrentSize));
 
-  uint32_t i;
-  if (mFull) {
-    for (i = 0; i < mBufferSize; i++) {
-      // if full, fill the buffer starting from mCurrent (which'll be
-      // oldest) wrapping around the buffer to the most recent.
-      messageArray[i] = mMessages[(mCurrent + i) % mBufferSize];
-      NS_ADDREF(messageArray[i]);
-    }
-  } else {
-    for (i = 0; i < mCurrent; i++) {
-      messageArray[i] = mMessages[i];
-      NS_ADDREF(messageArray[i]);
-    }
-  }
-  *aCount = resultSize;
+  uint32_t i = 0;
+  for (MessageElement* e = mMessages.getFirst(); e != nullptr; e = e->getNext()) {
+    nsCOMPtr<nsIConsoleMessage> m = e->Get();
+    m.forget(&messageArray[i]);
+    i++;
+  };
+
+  MOZ_ASSERT(i == mCurrentSize);
+
+  *aCount = i;
   *aMessages = messageArray;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsConsoleService::RegisterListener(nsIConsoleListener* aListener)
 {
@@ -475,26 +437,17 @@ nsConsoleService::Reset()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   /*
    * Make sure nobody trips into the buffer while it's being reset
    */
   MutexAutoLock lock(mLock);
 
-  mCurrent = 0;
-  mFull = false;
-
-  /*
-   * Free all messages stored so far (cf. destructor)
-   */
-  for (uint32_t i = 0; i < mBufferSize && mMessages[i]; i++) {
-    NS_RELEASE(mMessages[i]);
-  }
-
+  ClearMessages();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsConsoleService::Observe(nsISupports* aSubject, const char* aTopic,
                           const char16_t* aData)
 {
   if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
--- a/xpcom/base/nsConsoleService.h
+++ b/xpcom/base/nsConsoleService.h
@@ -55,31 +55,55 @@ public:
   virtual nsresult LogMessageWithMode(nsIConsoleMessage* aMessage,
                                       OutputMode aOutputMode);
 
   typedef nsInterfaceHashtable<nsISupportsHashKey,
                                nsIConsoleListener> ListenerHash;
   void CollectCurrentListeners(nsCOMArray<nsIConsoleListener>& aListeners);
 
 private:
+  class MessageElement : public mozilla::LinkedListElement<MessageElement>
+  {
+  public:
+    explicit MessageElement(nsIConsoleMessage* aMessage) : mMessage(aMessage)
+    {}
+
+    nsIConsoleMessage* Get()
+    {
+      return mMessage.get();
+    }
+
+    already_AddRefed<nsIConsoleMessage> forget()
+    {
+      return mMessage.forget();
+    }
+
+    ~MessageElement();
+
+  private:
+    nsCOMPtr<nsIConsoleMessage> mMessage;
+
+    MessageElement(const MessageElement&) = delete;
+    MessageElement& operator=(const MessageElement&) = delete;
+    MessageElement(MessageElement&&) = delete;
+    MessageElement& operator=(MessageElement&&) = delete;
+  };
+
   ~nsConsoleService();
 
   void ClearMessagesForWindowID(const uint64_t innerID);
+  void ClearMessages();
 
-  // Circular buffer of saved messages
-  nsIConsoleMessage** mMessages;
+  mozilla::LinkedList<MessageElement> mMessages;
 
-  // How big?
-  uint32_t mBufferSize;
+  // The current size of mMessages.
+  uint32_t mCurrentSize;
 
-  // Index of slot in mMessages that'll be filled by *next* log message
-  uint32_t mCurrent;
-
-  // Is the buffer full? (Has mCurrent wrapped around at least once?)
-  bool mFull;
+  // The maximum size of mMessages.
+  uint32_t mMaximumSize;
 
   // Are we currently delivering a console message on the main thread? If
   // so, we suppress incoming messages on the main thread only, to avoid
   // infinite repitition.
   bool mDeliveringMessage;
 
   // Listeners to notify whenever a new message is logged.
   ListenerHash mListeners;
--- a/xpcom/tests/TestNsRefPtr.cpp
+++ b/xpcom/tests/TestNsRefPtr.cpp
@@ -446,25 +446,25 @@ main()
 
       nsRefPtr<Foo> foo2p( do_QueryObject(new Foo) );
 
       if ( foo1p != foo2p )
         printf("foo1p != foo2p\n");
       else
         printf("foo1p == foo2p\n");
 
-      printf("\n### Test  7.5: can you compare a |nsCOMPtr| with nullptr [!=]?\n");
-      if ( foo1p != nullptr )
-      	printf("foo1p != nullptr\n");
-      if ( nullptr != foo1p )
-      	printf("nullptr != foo1p\n");
-      if ( foo1p == nullptr )
-      	printf("foo1p == nullptr\n");
-      if ( nullptr == foo1p )
-      	printf("nullptr == foo1p\n");
+      printf("\n### Test  7.5: can you compare a |nsCOMPtr| with NULL, 0, nullptr [!=]?\n");
+      if ( foo1p != 0 )
+      	printf("foo1p != 0\n");
+      if ( 0 != foo1p )
+      	printf("0 != foo1p\n");
+      if ( foo1p == 0 )
+      	printf("foo1p == 0\n");
+      if ( 0 == foo1p )
+      	printf("0 == foo1p\n");
 
 
       Foo* raw_foo2p = foo2p.get();
 
       printf("\n### Test  8: can you compare a |nsCOMPtr| with a raw interface pointer [!=]?\n");
       if ( foo1p.get() != raw_foo2p )
         printf("foo1p != raw_foo2p\n");
       else
@@ -495,18 +495,18 @@ main()
 #endif
 
       printf("\n### Test 12: bare pointer test?\n");
       if ( foo1p )
         printf("foo1p is not NULL\n");
       else
         printf("foo1p is NULL\n");
 
-      printf("\n### Test 13: null pointer test?\n");
-      if ( foo1p == nullptr )
+      printf("\n### Test 13: numeric pointer test?\n");
+      if ( foo1p == 0 )
         printf("foo1p is NULL\n");
       else
         printf("foo1p is not NULL\n");
 
 #if 0
 			if ( foo1p == 1 )
 				printf("foo1p allowed compare with in\n");
 #endif