merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 23 Mar 2015 13:18:35 +0100
changeset 265328 bc85c479668a86c0851bfbf9992ab78fcc51c479
parent 265279 06f13b15a69ba75bebae7b8fc91e7c79d583b011 (current diff)
parent 265327 5e0659bb1220312e6703f38370810d0448358358 (diff)
child 265330 358d0174fee4da8eeeb3d5e75c6ba3e75545e3e2
child 265365 998d7bdb1f15e1a7ca38b7d79e15bcfb6860a680
child 265411 509d65f46712557f110ffb21b6e252c0c43180e2
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
dom/media/test/test_eme_access_control.html
toolkit/modules/GMPUtils.jsm
toolkit/mozapps/extensions/test/browser/browser_gmpProvider.js
--- a/accessible/atk/nsMaiInterfaceImage.cpp
+++ b/accessible/atk/nsMaiInterfaceImage.cpp
@@ -7,54 +7,62 @@
 #include "InterfaceInitFuncs.h"
 
 #include "AccessibleWrap.h"
 #include "ImageAccessible.h"
 #include "mozilla/Likely.h"
 #include "nsMai.h"
 #include "nsIAccessibleTypes.h"
 #include "nsIURI.h"
+#include "ProxyAccessible.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 extern "C" {
 const gchar* getDescriptionCB(AtkObject* aAtkObj);
 
 static void
 getImagePositionCB(AtkImage* aImage, gint* aAccX, gint* aAccY,
                    AtkCoordType aCoordType)
 {
-  AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aImage));
-  if (!accWrap || !accWrap->IsImage())
-    return;
-
-  ImageAccessible* image = accWrap->AsImage();
+  nsIntPoint pos;
   uint32_t geckoCoordType = (aCoordType == ATK_XY_WINDOW) ?
     nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE :
     nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE;
-  nsIntPoint pos = image->Position(geckoCoordType);
+
+  AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aImage));
+  if (accWrap && accWrap->IsImage()) {
+    ImageAccessible* image = accWrap->AsImage();
+    pos = image->Position(geckoCoordType);
+  } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aImage))) {
+    pos = proxy->ImagePosition(geckoCoordType);
+  }
+
   *aAccX = pos.x;
   *aAccY = pos.y;
 }
 
 static const gchar*
 getImageDescriptionCB(AtkImage* aImage)
 {
   return getDescriptionCB(ATK_OBJECT(aImage));
 }
 
 static void
 getImageSizeCB(AtkImage* aImage, gint* aAccWidth, gint* aAccHeight)
 {
+  nsIntSize size;
   AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aImage));
-  if (!accWrap || !accWrap->IsImage())
-    return;
+  if (accWrap && accWrap->IsImage()) {
+    size = accWrap->AsImage()->Size();
+  } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aImage))) {
+    size = proxy->ImageSize();
+  }
 
-  nsIntSize size = accWrap->AsImage()->Size();
   *aAccWidth = size.width;
   *aAccHeight = size.height;
 }
 
 } // extern "C"
 
 void
 imageInterfaceInitCB(AtkImageIface* aIface)
--- a/accessible/ipc/DocAccessibleChild.cpp
+++ b/accessible/ipc/DocAccessibleChild.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DocAccessibleChild.h"
 
 #include "Accessible-inl.h"
 #include "ProxyAccessible.h"
 #include "Relation.h"
 #include "HyperTextAccessible-inl.h"
+#include "ImageAccessible.h"
 #include "nsIPersistentProperties2.h"
 #include "nsISimpleEnumerator.h"
 
 namespace mozilla {
 namespace a11y {
 
 static uint32_t
 InterfacesFor(Accessible* aAcc)
@@ -41,29 +42,36 @@ SerializeTree(Accessible* aRoot, nsTArra
     childCount = 0;
 
   aTree.AppendElement(AccessibleData(id, role, childCount, interfaces));
   for (uint32_t i = 0; i < childCount; i++)
     SerializeTree(aRoot->GetChildAt(i), aTree);
 }
 
 Accessible*
-DocAccessibleChild::IdToAccessible(const uint64_t& aID)
+DocAccessibleChild::IdToAccessible(const uint64_t& aID) const
 {
   return mDoc->GetAccessibleByUniqueID(reinterpret_cast<void*>(aID));
 }
 
 HyperTextAccessible*
-DocAccessibleChild::IdToHyperTextAccessible(const uint64_t& aID)
+DocAccessibleChild::IdToHyperTextAccessible(const uint64_t& aID) const
 {
   Accessible* acc = IdToAccessible(aID);
   MOZ_ASSERT(!acc || acc->IsHyperText());
   return acc ? acc->AsHyperText() : nullptr;
 }
 
+ImageAccessible*
+DocAccessibleChild::IdToImageAccessible(const uint64_t& aID) const
+{
+  Accessible* acc = IdToAccessible(aID);
+  return (acc && acc->IsImage()) ? acc->AsImage() : nullptr;
+}
+
 void
 DocAccessibleChild::ShowEvent(AccShowEvent* aShowEvent)
 {
   Accessible* parent = aShowEvent->Parent();
   uint64_t parentID = parent->IsDoc() ? 0 : reinterpret_cast<uint64_t>(parent->UniqueID());
   uint32_t idxInParent = aShowEvent->GetAccessible()->IndexInParent();
   nsTArray<AccessibleData> shownTree;
   ShowEventData data(parentID, idxInParent, shownTree);
@@ -579,10 +587,36 @@ DocAccessibleChild::RecvPasteText(const 
   HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
   if (acc && acc->IsTextRole()) {
     acc->PasteText(aPosition);
   }
 
   return true;
 }
 
+bool
+DocAccessibleChild::RecvImagePosition(const uint64_t& aID,
+                                      const uint32_t& aCoordType,
+                                      nsIntPoint* aRetVal)
+{
+  ImageAccessible* acc = IdToImageAccessible(aID);
+  if (acc) {
+    *aRetVal = acc->Position(aCoordType);
+  }
+
+  return true;
+}
+
+bool
+DocAccessibleChild::RecvImageSize(const uint64_t& aID,
+                                  nsIntSize* aRetVal)
+{
+
+  ImageAccessible* acc = IdToImageAccessible(aID);
+  if (acc) {
+    *aRetVal = acc->Size();
+  }
+
+  return true;
+}
+
 }
 }
--- a/accessible/ipc/DocAccessibleChild.h
+++ b/accessible/ipc/DocAccessibleChild.h
@@ -10,16 +10,17 @@
 #include "mozilla/a11y/DocAccessible.h"
 #include "mozilla/a11y/PDocAccessibleChild.h"
 #include "nsISupportsImpl.h"
 
 namespace mozilla {
 namespace a11y {
 class Accessible;
 class HyperTextAccessible;
+class ImageAccessible;
 
 class AccShowEvent;
 
   /*
    * These objects handle content side communication for an accessible document,
    * and their lifetime is the same as the document they represent.
    */
 class DocAccessibleChild : public PDocAccessibleChild
@@ -29,19 +30,16 @@ public:
     mDoc(aDoc)
   { MOZ_COUNT_CTOR(DocAccessibleChild); }
   ~DocAccessibleChild()
   {
     mDoc->SetIPCDoc(nullptr);
     MOZ_COUNT_DTOR(DocAccessibleChild);
   }
 
-  Accessible* IdToAccessible(const uint64_t& aID);
-  HyperTextAccessible* IdToHyperTextAccessible(const uint64_t& aID);
-
   void ShowEvent(AccShowEvent* aShowEvent);
 
   /*
    * Return the state for the accessible with given ID.
    */
   virtual bool RecvState(const uint64_t& aID, uint64_t* aState) override;
 
   /*
@@ -179,17 +177,29 @@ public:
 
   virtual bool RecvDeleteText(const uint64_t& aID,
                               const int32_t& aStartPos,
                               const int32_t& aEndPos) override;
 
   virtual bool RecvPasteText(const uint64_t& aID,
                              const int32_t& aPosition) override;
 
+  virtual bool RecvImagePosition(const uint64_t& aID,
+                                 const uint32_t& aCoordType,
+                                 nsIntPoint* aRetVal) override;
+
+  virtual bool RecvImageSize(const uint64_t& aID,
+                             nsIntSize* aRetVal) override;
+
 private:
+
+  Accessible* IdToAccessible(const uint64_t& aID) const;
+  HyperTextAccessible* IdToHyperTextAccessible(const uint64_t& aID) const;
+  ImageAccessible* IdToImageAccessible(const uint64_t& aID) const;
+
   bool PersistentPropertiesToArray(nsIPersistentProperties* aProps,
                                    nsTArray<Attribute>* aAttributes);
 
   DocAccessible* mDoc;
 };
 
 }
 }
--- a/accessible/ipc/PDocAccessible.ipdl
+++ b/accessible/ipc/PDocAccessible.ipdl
@@ -3,16 +3,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PContent;
 
 include "mozilla/GfxMessageUtils.h";
 
+using struct nsIntPoint from "nsRect.h";
+using struct nsIntSize from "nsRect.h";
 using struct nsIntRect from "nsRect.h";
 
 namespace mozilla {
 namespace a11y {
 
 struct AccessibleData
 {
   uint64_t ID;
@@ -116,12 +118,15 @@ child:
                          int32_t aX, int32_t aY);
 
   prio(high) sync ReplaceText(uint64_t aID, nsString aText);
   prio(high) sync InsertText(uint64_t aID, nsString aText, int32_t aPosition);
   prio(high) sync CopyText(uint64_t aID, int32_t aStartPos, int32_t aEndPos);
   prio(high) sync CutText(uint64_t aID, int32_t aStartPos, int32_t aEndPos);
   prio(high) sync DeleteText(uint64_t aID, int32_t aStartPos, int32_t aEndPos);
   prio(high) sync PasteText(uint64_t aID, int32_t aPosition);
+
+  prio(high) sync ImagePosition(uint64_t aID, uint32_t aCoordType) returns(nsIntPoint aRetVal);
+  prio(high) sync ImageSize(uint64_t aID) returns(nsIntSize aRetVal);
 };
 
 }
 }
--- a/accessible/ipc/ProxyAccessible.cpp
+++ b/accessible/ipc/ProxyAccessible.cpp
@@ -358,10 +358,26 @@ ProxyAccessible::DeleteText(int32_t aSta
 }
 
 void
 ProxyAccessible::PasteText(int32_t aPosition)
 {
   unused << mDoc->SendPasteText(mID, aPosition);
 }
 
+nsIntPoint
+ProxyAccessible::ImagePosition(uint32_t aCoordType)
+{
+  nsIntPoint retVal;
+  unused << mDoc->SendImagePosition(mID, aCoordType, &retVal);
+  return retVal;
+}
+
+nsIntSize
+ProxyAccessible::ImageSize()
+{
+  nsIntSize retVal;
+  unused << mDoc->SendImageSize(mID, &retVal);
+  return retVal;
+}
+
 }
 }
--- a/accessible/ipc/ProxyAccessible.h
+++ b/accessible/ipc/ProxyAccessible.h
@@ -172,16 +172,20 @@ public:
   void CopyText(int32_t aStartPos, int32_t aEndPos);
 
   void CutText(int32_t aStartPos, int32_t aEndPos);
 
   void DeleteText(int32_t aStartPos, int32_t aEndPos);
 
   void PasteText(int32_t aPosition);
 
+  nsIntPoint ImagePosition(uint32_t aCoordType);
+
+  nsIntSize ImageSize();
+
   /**
    * Allow the platform to store a pointers worth of data on us.
    */
   uintptr_t GetWrapper() const { return mWrapper; }
   void SetWrapper(uintptr_t aWrapper) { mWrapper = aWrapper; }
 
   /*
    * Return the ID of the accessible being proxied.
--- a/accessible/ipc/moz.build
+++ b/accessible/ipc/moz.build
@@ -21,13 +21,31 @@ if CONFIG['ACCESSIBILITY']:
         'ProxyAccessible.cpp'
     ]
 
     LOCAL_INCLUDES += [
         '../base',
         '../generic',
     ]
 
+    if CONFIG['MOZ_ENABLE_GTK']:
+        LOCAL_INCLUDES += [
+            '/accessible/atk',
+        ]
+    elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+        LOCAL_INCLUDES += [
+            '/accessible/windows/ia2',
+            '/accessible/windows/msaa',
+        ]
+    elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+        LOCAL_INCLUDES += [
+            '/accessible/mac',
+        ]
+    else:
+        LOCAL_INCLUDES += [
+            '/accessible/other',
+        ]
+
     FINAL_LIBRARY = 'xul'
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FAIL_ON_WARNINGS = True
--- a/dom/animation/test/chrome/test_animation_observers.html
+++ b/dom/animation/test/chrome/test_animation_observers.html
@@ -310,29 +310,34 @@ function assert_records(expected, desc) 
     var firstPlayer = players[0];
 
     // Wait for the single MutationRecord for the AnimationPlayer addition to
     // be delivered.
     yield await_frame();
     assert_records([{ added: [firstPlayer], changed: [], removed: [] }],
                    "records after transition start");
 
-    // Wait a bit longer for the transition to take effect.
+    // Wait for the AnimationPlayer to get going, then seek well into
+    // the transition.
     yield await_frame();
+    firstPlayer.currentTime = 50000;
 
     // Reverse the transition by setting the background-color back to its
     // original value.
     e.style.backgroundColor = "yellow";
 
     // The reversal should cause the creation of a new AnimationPlayer.
     players = e.getAnimationPlayers();
     is(players.length, 1, "getAnimationPlayers().length after transition reversal");
 
     var secondPlayer = players[0];
 
+    ok(firstPlayer != secondPlayer,
+       "second AnimationPlayer should be different from the first");
+
     // Wait for the single MutationRecord for the removal of the original
     // AnimationPlayer and the addition of the new AnimationPlayer to
     // be delivered.
     yield await_frame();
     assert_records([{ added: [secondPlayer], changed: [], removed: [firstPlayer] }],
                    "records after transition reversal");
 
     // Cancel the transition.
@@ -360,18 +365,20 @@ function assert_records(expected, desc) 
     is(players.length, 3, "getAnimationPlayers().length after transition starts");
 
     // Wait for the single MutationRecord for the AnimationPlayer additions to
     // be delivered.
     yield await_frame();
     assert_records([{ added: players, changed: [], removed: [] }],
                    "records after transition starts");
 
-    // Wait for the AnimationPlayers to get going.
+    // Wait for the AnimationPlayers to get going, then seek well into
+    // the transitions.
     yield await_frame();
+    players.forEach(p => p.currentTime = 50000);
 
     is(players.filter(p => p.playState == "running").length, 3, "number of running AnimationPlayers");
 
     // Cancel one of the transitions by setting transition-property.
     e.style.transitionProperty = "background-color, line-height";
 
     var colorPlayer  = players.filter(p => p.playState != "running");
     var otherPlayers = players.filter(p => p.playState == "running");
--- a/dom/cache/AutoUtils.cpp
+++ b/dom/cache/AutoUtils.cpp
@@ -2,90 +2,118 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/cache/AutoUtils.h"
 
 #include "mozilla/unused.h"
+#include "mozilla/dom/cache/CachePushStreamChild.h"
 #include "mozilla/dom/cache/CacheStreamControlParent.h"
 #include "mozilla/dom/cache/ReadStream.h"
 #include "mozilla/dom/cache/SavedTypes.h"
 #include "mozilla/dom/cache/StreamList.h"
 #include "mozilla/dom/cache/TypeUtils.h"
 #include "mozilla/ipc/FileDescriptorSetChild.h"
 #include "mozilla/ipc/FileDescriptorSetParent.h"
 #include "mozilla/ipc/PBackgroundParent.h"
 
 namespace {
 
 using mozilla::unused;
+using mozilla::dom::cache::CachePushStreamChild;
 using mozilla::dom::cache::PCacheReadStream;
 using mozilla::dom::cache::PCacheReadStreamOrVoid;
 using mozilla::ipc::FileDescriptor;
 using mozilla::ipc::FileDescriptorSetChild;
 using mozilla::ipc::FileDescriptorSetParent;
 using mozilla::ipc::OptionalFileDescriptorSet;
 
 enum CleanupAction
 {
-  ForgetFds,
-  DeleteFds
+  Forget,
+  Delete
 };
 
 void
 CleanupChildFds(PCacheReadStream& aReadStream, CleanupAction aAction)
 {
   if (aReadStream.fds().type() !=
       OptionalFileDescriptorSet::TPFileDescriptorSetChild) {
     return;
   }
 
   nsAutoTArray<FileDescriptor, 4> fds;
 
   FileDescriptorSetChild* fdSetActor =
     static_cast<FileDescriptorSetChild*>(aReadStream.fds().get_PFileDescriptorSetChild());
   MOZ_ASSERT(fdSetActor);
 
-  if (aAction == DeleteFds) {
+  if (aAction == Delete) {
     unused << fdSetActor->Send__delete__(fdSetActor);
   }
 
   // FileDescriptorSet doesn't clear its fds in its ActorDestroy, so we
   // unconditionally forget them here.  The fds themselves are auto-closed in
   // ~FileDescriptor since they originated in this process.
   fdSetActor->ForgetFileDescriptors(fds);
 }
 
 void
-CleanupChildFds(PCacheReadStreamOrVoid& aReadStreamOrVoid, CleanupAction aAction)
+CleanupChildPushStream(PCacheReadStream& aReadStream, CleanupAction aAction)
+{
+  if (!aReadStream.pushStreamChild()) {
+    return;
+  }
+
+  auto pushStream =
+    static_cast<CachePushStreamChild*>(aReadStream.pushStreamChild());
+
+  if (aAction == Delete) {
+    pushStream->StartDestroy();
+    return;
+  }
+
+  // If we send the stream, then we need to start it before forgetting about it.
+  pushStream->Start();
+}
+
+void
+CleanupChild(PCacheReadStream& aReadStream, CleanupAction aAction)
+{
+  CleanupChildFds(aReadStream, aAction);
+  CleanupChildPushStream(aReadStream, aAction);
+}
+
+void
+CleanupChild(PCacheReadStreamOrVoid& aReadStreamOrVoid, CleanupAction aAction)
 {
   if (aReadStreamOrVoid.type() == PCacheReadStreamOrVoid::Tvoid_t) {
     return;
   }
 
-  CleanupChildFds(aReadStreamOrVoid.get_PCacheReadStream(), aAction);
+  CleanupChild(aReadStreamOrVoid.get_PCacheReadStream(), aAction);
 }
 
 void
 CleanupParentFds(PCacheReadStream& aReadStream, CleanupAction aAction)
 {
   if (aReadStream.fds().type() !=
       OptionalFileDescriptorSet::TPFileDescriptorSetParent) {
     return;
   }
 
   nsAutoTArray<FileDescriptor, 4> fds;
 
   FileDescriptorSetParent* fdSetActor =
     static_cast<FileDescriptorSetParent*>(aReadStream.fds().get_PFileDescriptorSetParent());
   MOZ_ASSERT(fdSetActor);
 
-  if (aAction == DeleteFds) {
+  if (aAction == Delete) {
     unused << fdSetActor->Send__delete__(fdSetActor);
   }
 
   // FileDescriptorSet doesn't clear its fds in its ActorDestroy, so we
   // unconditionally forget them here.  The fds themselves are auto-closed in
   // ~FileDescriptor since they originated in this process.
   fdSetActor->ForgetFileDescriptors(fds);
 }
@@ -128,18 +156,18 @@ AutoChildRequest::AutoChildRequest(TypeU
 }
 
 AutoChildRequest::~AutoChildRequest()
 {
   if (mRequestOrVoid.type() != PCacheRequestOrVoid::TPCacheRequest) {
     return;
   }
 
-  CleanupAction action = mSent ? ForgetFds : DeleteFds;
-  CleanupChildFds(mRequestOrVoid.get_PCacheRequest().body(), action);
+  CleanupAction action = mSent ? Forget : Delete;
+  CleanupChild(mRequestOrVoid.get_PCacheRequest().body(), action);
 }
 
 void
 AutoChildRequest::Add(InternalRequest* aRequest, BodyAction aBodyAction,
                       ReferrerAction aReferrerAction, SchemeAction aSchemeAction,
                       ErrorResult& aRv)
 {
   MOZ_ASSERT(!mSent);
@@ -168,19 +196,19 @@ AutoChildRequestList::AutoChildRequestLi
                                            uint32_t aCapacity)
   : AutoChildBase(aTypeUtils)
 {
   mRequestList.SetCapacity(aCapacity);
 }
 
 AutoChildRequestList::~AutoChildRequestList()
 {
-  CleanupAction action = mSent ? ForgetFds : DeleteFds;
+  CleanupAction action = mSent ? Forget : Delete;
   for (uint32_t i = 0; i < mRequestList.Length(); ++i) {
-    CleanupChildFds(mRequestList[i].body(), action);
+    CleanupChild(mRequestList[i].body(), action);
   }
 }
 
 void
 AutoChildRequestList::Add(InternalRequest* aRequest, BodyAction aBodyAction,
                           ReferrerAction aReferrerAction,
                           SchemeAction aSchemeAction, ErrorResult& aRv)
 {
@@ -218,19 +246,19 @@ AutoChildRequestResponse::AutoChildReque
   // Default IPC-generated constructor does not initialize these correctly
   // and we check them later when cleaning up.
   mRequestResponse.request().body() = void_t();
   mRequestResponse.response().body() = void_t();
 }
 
 AutoChildRequestResponse::~AutoChildRequestResponse()
 {
-  CleanupAction action = mSent ? ForgetFds : DeleteFds;
-  CleanupChildFds(mRequestResponse.request().body(), action);
-  CleanupChildFds(mRequestResponse.response().body(), action);
+  CleanupAction action = mSent ? Forget : Delete;
+  CleanupChild(mRequestResponse.request().body(), action);
+  CleanupChild(mRequestResponse.response().body(), action);
 }
 
 void
 AutoChildRequestResponse::Add(InternalRequest* aRequest, BodyAction aBodyAction,
                               ReferrerAction aReferrerAction,
                               SchemeAction aSchemeAction, ErrorResult& aRv)
 {
   MOZ_ASSERT(!mSent);
@@ -306,17 +334,17 @@ AutoParentRequestList::AutoParentRequest
                                              uint32_t aCapacity)
   : AutoParentBase(aManager)
 {
   mRequestList.SetCapacity(aCapacity);
 }
 
 AutoParentRequestList::~AutoParentRequestList()
 {
-  CleanupAction action = mSent ? ForgetFds : DeleteFds;
+  CleanupAction action = mSent ? Forget : Delete;
   for (uint32_t i = 0; i < mRequestList.Length(); ++i) {
     CleanupParentFds(mRequestList[i].body(), action);
   }
 }
 
 void
 AutoParentRequestList::Add(const SavedRequest& aSavedRequest,
                            StreamList* aStreamList)
@@ -350,17 +378,17 @@ AutoParentResponseList::AutoParentRespon
                                                uint32_t aCapacity)
   : AutoParentBase(aManager)
 {
   mResponseList.SetCapacity(aCapacity);
 }
 
 AutoParentResponseList::~AutoParentResponseList()
 {
-  CleanupAction action = mSent ? ForgetFds : DeleteFds;
+  CleanupAction action = mSent ? Forget : Delete;
   for (uint32_t i = 0; i < mResponseList.Length(); ++i) {
     CleanupParentFds(mResponseList[i].body(), action);
   }
 }
 
 void
 AutoParentResponseList::Add(const SavedResponse& aSavedResponse,
                             StreamList* aStreamList)
@@ -397,17 +425,17 @@ AutoParentResponseOrVoid::AutoParentResp
 }
 
 AutoParentResponseOrVoid::~AutoParentResponseOrVoid()
 {
   if (mResponseOrVoid.type() != PCacheResponseOrVoid::TPCacheResponse) {
     return;
   }
 
-  CleanupAction action = mSent ? ForgetFds : DeleteFds;
+  CleanupAction action = mSent ? Forget : Delete;
   CleanupParentFds(mResponseOrVoid.get_PCacheResponse().body(), action);
 }
 
 void
 AutoParentResponseOrVoid::Add(const SavedResponse& aSavedResponse,
                               StreamList* aStreamList)
 {
   MOZ_ASSERT(!mSent);
--- a/dom/cache/Cache.cpp
+++ b/dom/cache/Cache.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/dom/Headers.h"
 #include "mozilla/dom/InternalResponse.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/Response.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/CacheBinding.h"
 #include "mozilla/dom/cache/AutoUtils.h"
 #include "mozilla/dom/cache/CacheChild.h"
+#include "mozilla/dom/cache/CachePushStreamChild.h"
 #include "mozilla/dom/cache/ReadStream.h"
 #include "mozilla/dom/cache/TypeUtils.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/unused.h"
 #include "nsIGlobalObject.h"
 #include "nsNetUtil.h"
 
@@ -521,16 +522,28 @@ Cache::GetGlobalObject() const
 #ifdef DEBUG
 void
 Cache::AssertOwningThread() const
 {
   NS_ASSERT_OWNINGTHREAD(Cache);
 }
 #endif
 
+CachePushStreamChild*
+Cache::CreatePushStream(nsIAsyncInputStream* aStream)
+{
+  NS_ASSERT_OWNINGTHREAD(Cache);
+  MOZ_ASSERT(mActor);
+  MOZ_ASSERT(aStream);
+  auto actor = mActor->SendPCachePushStreamConstructor(
+    new CachePushStreamChild(mActor->GetFeature(), aStream));
+  MOZ_ASSERT(actor);
+  return static_cast<CachePushStreamChild*>(actor);
+}
+
 void
 Cache::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
 {
   // Do nothing.  The Promise will automatically drop the ref to us after
   // calling the callback.  This is what we want as we only registered in order
   // to be held alive via the Promise handle.
 }
 
--- a/dom/cache/Cache.h
+++ b/dom/cache/Cache.h
@@ -34,18 +34,18 @@ template<typename T> class Sequence;
 namespace cache {
 
 class CacheChild;
 class PCacheRequest;
 class PCacheResponse;
 class PCacheResponseOrVoid;
 
 class Cache final : public PromiseNativeHandler
-                      , public nsWrapperCache
-                      , public TypeUtils
+                  , public nsWrapperCache
+                  , public TypeUtils
 {
 public:
   Cache(nsIGlobalObject* aGlobal, CacheChild* aActor);
 
   // webidl interface methods
   already_AddRefed<Promise>
   Match(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions,
         ErrorResult& aRv);
@@ -92,16 +92,19 @@ public:
   // TypeUtils methods
   virtual nsIGlobalObject*
   GetGlobalObject() const override;
 
 #ifdef DEBUG
   virtual void AssertOwningThread() const override;
 #endif
 
+  virtual CachePushStreamChild*
+  CreatePushStream(nsIAsyncInputStream* aStream) override;
+
   // PromiseNativeHandler methods
   virtual void
   ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   virtual void
   RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
 private:
--- a/dom/cache/CacheChild.cpp
+++ b/dom/cache/CacheChild.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 "mozilla/dom/cache/CacheChild.h"
 
 #include "mozilla/unused.h"
 #include "mozilla/dom/cache/ActorUtils.h"
 #include "mozilla/dom/cache/Cache.h"
+#include "mozilla/dom/cache/PCachePushStreamChild.h"
 #include "mozilla/dom/cache/StreamUtils.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 // Declared in ActorUtils.h
 PCacheChild*
@@ -89,16 +90,30 @@ CacheChild::ActorDestroy(ActorDestroyRea
     listener->DestroyInternal(this);
     // Cache listener should call ClearListener() in DestroyInternal()
     MOZ_ASSERT(!mListener);
   }
 
   RemoveFeature();
 }
 
+PCachePushStreamChild*
+CacheChild::AllocPCachePushStreamChild()
+{
+  MOZ_CRASH("CachePushStreamChild should be manually constructed.");
+  return nullptr;
+}
+
+bool
+CacheChild::DeallocPCachePushStreamChild(PCachePushStreamChild* aActor)
+{
+  delete aActor;
+  return true;
+}
+
 bool
 CacheChild::RecvMatchResponse(const RequestId& requestId, const nsresult& aRv,
                               const PCacheResponseOrVoid& aResponse)
 {
   NS_ASSERT_OWNINGTHREAD(CacheChild);
 
   AddFeatureToStreamChild(aResponse, GetFeature());
 
--- a/dom/cache/CacheChild.h
+++ b/dom/cache/CacheChild.h
@@ -12,17 +12,17 @@
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class Cache;
 
 class CacheChild final : public PCacheChild
-                           , public ActorChild
+                       , public ActorChild
 {
 public:
   CacheChild();
   ~CacheChild();
 
   void SetListener(Cache* aListener);
 
   // Must be called by the associated Cache listener in its ActorDestroy()
@@ -36,16 +36,22 @@ public:
   // actor destruction asynchronously from the parent-side.
   virtual void StartDestroy() override;
 
 private:
   // PCacheChild methods
   virtual void
   ActorDestroy(ActorDestroyReason aReason) override;
 
+  virtual PCachePushStreamChild*
+  AllocPCachePushStreamChild() override;
+
+  virtual bool
+  DeallocPCachePushStreamChild(PCachePushStreamChild* aActor) override;
+
   virtual bool
   RecvMatchResponse(const RequestId& requestId, const nsresult& aRv,
                     const PCacheResponseOrVoid& aResponse) override;
   virtual bool
   RecvMatchAllResponse(const RequestId& requestId, const nsresult& aRv,
                        nsTArray<PCacheResponse>&& responses) override;
   virtual bool
   RecvAddAllResponse(const RequestId& requestId,
--- a/dom/cache/CacheParent.cpp
+++ b/dom/cache/CacheParent.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/cache/CacheParent.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/cache/AutoUtils.h"
+#include "mozilla/dom/cache/CachePushStreamParent.h"
 #include "mozilla/dom/cache/CacheStreamControlParent.h"
 #include "mozilla/dom/cache/ReadStream.h"
 #include "mozilla/dom/cache/SavedTypes.h"
 #include "mozilla/dom/cache/StreamList.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/PBackgroundParent.h"
 #include "mozilla/ipc/FileDescriptorSetParent.h"
 #include "mozilla/ipc/PFileDescriptorSetParent.h"
@@ -56,16 +57,29 @@ CacheParent::ActorDestroy(ActorDestroyRe
     mFetchPutList[i]->ClearListener();
   }
   mFetchPutList.Clear();
   mManager->RemoveListener(this);
   mManager->ReleaseCacheId(mCacheId);
   mManager = nullptr;
 }
 
+PCachePushStreamParent*
+CacheParent::AllocPCachePushStreamParent()
+{
+  return CachePushStreamParent::Create();
+}
+
+bool
+CacheParent::DeallocPCachePushStreamParent(PCachePushStreamParent* aActor)
+{
+  delete aActor;
+  return true;
+}
+
 bool
 CacheParent::RecvTeardown()
 {
   if (!Send__delete__(this)) {
     // child process is gone, warn and allow actor to clean up normally
     NS_WARNING("Cache failed to send delete.");
   }
   return true;
@@ -254,23 +268,37 @@ CacheParent::OnFetchPut(FetchPut* aFetch
 
 already_AddRefed<nsIInputStream>
 CacheParent::DeserializeCacheStream(const PCacheReadStreamOrVoid& aStreamOrVoid)
 {
   if (aStreamOrVoid.type() == PCacheReadStreamOrVoid::Tvoid_t) {
     return nullptr;
   }
 
+  nsCOMPtr<nsIInputStream> stream;
   const PCacheReadStream& readStream = aStreamOrVoid.get_PCacheReadStream();
 
-  nsCOMPtr<nsIInputStream> stream = ReadStream::Create(readStream);
+  // Option 1: A push stream actor was sent for nsPipe data
+  if (readStream.pushStreamParent()) {
+    MOZ_ASSERT(!readStream.controlParent());
+    CachePushStreamParent* pushStream =
+      static_cast<CachePushStreamParent*>(readStream.pushStreamParent());
+    stream = pushStream->TakeReader();
+    MOZ_ASSERT(stream);
+    return stream.forget();
+  }
+
+  // Option 2: One of our own ReadStreams was passed back to us with a stream
+  //           control actor.
+  stream = ReadStream::Create(readStream);
   if (stream) {
     return stream.forget();
   }
 
+  // Option 3: A stream was serialized using normal methods.
   nsAutoTArray<FileDescriptor, 4> fds;
   if (readStream.fds().type() ==
       OptionalFileDescriptorSet::TPFileDescriptorSetChild) {
 
     FileDescriptorSetParent* fdSetActor =
       static_cast<FileDescriptorSetParent*>(readStream.fds().get_PFileDescriptorSetParent());
     MOZ_ASSERT(fdSetActor);
 
--- a/dom/cache/CacheParent.h
+++ b/dom/cache/CacheParent.h
@@ -17,26 +17,28 @@ template <class T> class nsRefPtr;
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 struct SavedResponse;
 
 class CacheParent final : public PCacheParent
-                            , public Manager::Listener
-                            , public FetchPut::Listener
+                        , public Manager::Listener
+                        , public FetchPut::Listener
 {
 public:
   CacheParent(cache::Manager* aManager, CacheId aCacheId);
   virtual ~CacheParent();
 
 private:
   // PCacheParent method
   virtual void ActorDestroy(ActorDestroyReason aReason) override;
+  virtual PCachePushStreamParent* AllocPCachePushStreamParent() override;
+  virtual bool DeallocPCachePushStreamParent(PCachePushStreamParent* aActor) override;
   virtual bool RecvTeardown() override;
   virtual bool
   RecvMatch(const RequestId& aRequestId, const PCacheRequest& aRequest,
             const PCacheQueryParams& aParams) override;
   virtual bool
   RecvMatchAll(const RequestId& aRequestId, const PCacheRequestOrVoid& aRequest,
                const PCacheQueryParams& aParams) override;
   virtual bool
new file mode 100644
--- /dev/null
+++ b/dom/cache/CachePushStreamChild.cpp
@@ -0,0 +1,259 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/cache/CachePushStreamChild.h"
+
+#include "mozilla/unused.h"
+#include "nsIAsyncInputStream.h"
+#include "nsICancelableRunnable.h"
+#include "nsIThread.h"
+#include "nsStreamUtils.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class CachePushStreamChild::Callback final : public nsIInputStreamCallback
+                                           , public nsICancelableRunnable
+{
+public:
+  explicit Callback(CachePushStreamChild* aActor)
+    : mActor(aActor)
+    , mOwningThread(NS_GetCurrentThread())
+  {
+    MOZ_ASSERT(mActor);
+  }
+
+  NS_IMETHOD
+  OnInputStreamReady(nsIAsyncInputStream* aStream) override
+  {
+    // any thread
+    if (mOwningThread == NS_GetCurrentThread()) {
+      return Run();
+    }
+
+    // If this fails, then it means the owning thread is a Worker that has
+    // been shutdown.  Its ok to lose the event in this case because the
+    // CachePushStreamChild listens for this event through the Feature.
+    nsresult rv = mOwningThread->Dispatch(this, nsIThread::DISPATCH_NORMAL);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to dispatch stream readable event to owning thread");
+    }
+
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    MOZ_ASSERT(mOwningThread == NS_GetCurrentThread());
+    if (mActor) {
+      mActor->OnStreamReady(this);
+    }
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  Cancel() override
+  {
+    // Cancel() gets called when the Worker thread is being shutdown.  We have
+    // nothing to do here because CachePushStreamChild handles this case via
+    // the Feature.
+    return NS_OK;
+  }
+
+  void
+  ClearActor()
+  {
+    MOZ_ASSERT(mOwningThread == NS_GetCurrentThread());
+    MOZ_ASSERT(mActor);
+    mActor = nullptr;
+  }
+
+private:
+  ~Callback()
+  {
+    // called on any thread
+
+    // ClearActor() should be called before the Callback is destroyed
+    MOZ_ASSERT(!mActor);
+  }
+
+  CachePushStreamChild* mActor;
+  nsCOMPtr<nsIThread> mOwningThread;
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(CachePushStreamChild::Callback, nsIInputStreamCallback,
+                                                  nsIRunnable,
+                                                  nsICancelableRunnable);
+
+CachePushStreamChild::CachePushStreamChild(Feature* aFeature,
+                                           nsIAsyncInputStream* aStream)
+  : mStream(aStream)
+  , mClosed(false)
+{
+  MOZ_ASSERT(mStream);
+  MOZ_ASSERT_IF(!NS_IsMainThread(), aFeature);
+  SetFeature(aFeature);
+}
+
+CachePushStreamChild::~CachePushStreamChild()
+{
+  NS_ASSERT_OWNINGTHREAD(CachePushStreamChild);
+  MOZ_ASSERT(mClosed);
+  MOZ_ASSERT(!mCallback);
+}
+
+void
+CachePushStreamChild::Start()
+{
+  DoRead();
+}
+
+void
+CachePushStreamChild::StartDestroy()
+{
+  // called if we are running on a Worker and the thread gets shutdown
+  OnEnd(NS_ERROR_ABORT);
+}
+
+void
+CachePushStreamChild::ActorDestroy(ActorDestroyReason aReason)
+{
+  NS_ASSERT_OWNINGTHREAD(CachePushStreamChild);
+
+  // If the parent side runs into a problem then the actor will be destroyed.
+  // In this case we have not run OnEnd(), so still need to close the input
+  // stream.
+  if (!mClosed) {
+    mStream->CloseWithStatus(NS_ERROR_ABORT);
+    mClosed = true;
+  }
+
+  if (mCallback) {
+    mCallback->ClearActor();
+    mCallback = nullptr;
+  }
+
+  RemoveFeature();
+}
+
+void
+CachePushStreamChild::DoRead()
+{
+  NS_ASSERT_OWNINGTHREAD(CachePushStreamChild);
+  MOZ_ASSERT(!mClosed);
+  MOZ_ASSERT(!mCallback);
+
+  // The input stream (likely a pipe) probably uses a segment size of
+  // 4kb.  If there is data already buffered it would be nice to aggregate
+  // multiple segments into a single IPC call.  Conversely, don't send too
+  // too large of a buffer in a single call to avoid spiking memory.
+  static const uint64_t kMaxBytesPerMessage = 32 * 1024;
+  static_assert(kMaxBytesPerMessage <= static_cast<uint64_t>(UINT32_MAX),
+                "kMaxBytesPerMessage must cleanly cast to uint32_t");
+
+  while (!mClosed) {
+    // Use non-auto here as we're unlikely to hit stack storage with the
+    // sizes we are sending.  Also, it would be nice to avoid another copy
+    // to the IPC layer which we avoid if we use COW strings.  Unfortunately
+    // IPC does not seem to support passing dependent storage types.
+    nsCString buffer;
+
+    uint64_t available = 0;
+    nsresult rv = mStream->Available(&available);
+    if (NS_FAILED(rv)) {
+      OnEnd(rv);
+      return;
+    }
+
+    if (available == 0) {
+      Wait();
+      return;
+    }
+
+    uint32_t expectedBytes =
+      static_cast<uint32_t>(std::min(available, kMaxBytesPerMessage));
+
+    buffer.SetLength(expectedBytes);
+
+    uint32_t bytesRead = 0;
+    rv = mStream->Read(buffer.BeginWriting(), buffer.Length(), &bytesRead);
+    buffer.SetLength(bytesRead);
+
+    // If we read any data from the stream, send it across.
+    if (!buffer.IsEmpty()) {
+      unused << SendBuffer(buffer);
+    }
+
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+      Wait();
+      return;
+    }
+
+    // Any other error or zero-byte read indicates end-of-stream
+    if (NS_FAILED(rv) || buffer.IsEmpty()) {
+      OnEnd(rv);
+      return;
+    }
+  }
+}
+
+void
+CachePushStreamChild::Wait()
+{
+  NS_ASSERT_OWNINGTHREAD(CachePushStreamChild);
+  MOZ_ASSERT(!mClosed);
+  MOZ_ASSERT(!mCallback);
+
+  // Set mCallback immediately instead of waiting for success.  Its possible
+  // AsyncWait() will callback synchronously.
+  mCallback = new Callback(this);
+  nsresult rv = mStream->AsyncWait(mCallback, 0, 0, nullptr);
+  if (NS_FAILED(rv)) {
+    OnEnd(rv);
+    return;
+  }
+}
+
+void
+CachePushStreamChild::OnStreamReady(Callback* aCallback)
+{
+  NS_ASSERT_OWNINGTHREAD(CachePushStreamChild);
+  MOZ_ASSERT(mCallback);
+  MOZ_ASSERT(aCallback == mCallback);
+  mCallback->ClearActor();
+  mCallback = nullptr;
+  DoRead();
+}
+
+void
+CachePushStreamChild::OnEnd(nsresult aRv)
+{
+  NS_ASSERT_OWNINGTHREAD(CachePushStreamChild);
+  MOZ_ASSERT(aRv != NS_BASE_STREAM_WOULD_BLOCK);
+
+  if (mClosed) {
+    return;
+  }
+
+  mClosed = true;
+
+  mStream->CloseWithStatus(aRv);
+
+  if (aRv == NS_BASE_STREAM_CLOSED) {
+    aRv = NS_OK;
+  }
+
+  // This will trigger an ActorDestroy() from the parent side
+  unused << SendClose(aRv);
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/CachePushStreamChild.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_cache_CachePushStreamChild_h
+#define mozilla_dom_cache_CachePushStreamChild_h
+
+#include "mozilla/dom/cache/ActorChild.h"
+#include "mozilla/dom/cache/PCachePushStreamChild.h"
+#include "nsCOMPtr.h"
+
+class nsIAsyncInputStream;
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class CachePushStreamChild final : public PCachePushStreamChild
+                                 , public ActorChild
+{
+public:
+  CachePushStreamChild(Feature* aFeature, nsIAsyncInputStream* aStream);
+  ~CachePushStreamChild();
+
+  virtual void StartDestroy() override;
+
+  void Start();
+
+private:
+  class Callback;
+
+  // PCachePushStreamChild methods
+  virtual void
+  ActorDestroy(ActorDestroyReason aReason) override;
+
+  void DoRead();
+
+  void Wait();
+
+  void OnStreamReady(Callback* aCallback);
+
+  void OnEnd(nsresult aRv);
+
+  nsCOMPtr<nsIAsyncInputStream> mStream;
+  nsRefPtr<Callback> mCallback;
+  bool mClosed;
+
+  NS_DECL_OWNINGTHREAD
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_CachePushStreamChild_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/CachePushStreamParent.cpp
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/cache/CachePushStreamParent.h"
+
+#include "mozilla/unused.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIPipe.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+// static
+CachePushStreamParent*
+CachePushStreamParent::Create()
+{
+  // use async versions for both reader and writer even though we are
+  // opening the writer as an infinite stream.  We want to be able to
+  // use CloseWithStatus() to communicate errors through the pipe.
+  nsCOMPtr<nsIAsyncInputStream> reader;
+  nsCOMPtr<nsIAsyncOutputStream> writer;
+
+  // Use an "infinite" pipe because we cannot apply back-pressure through
+  // the async IPC layer at the moment.  Blocking the IPC worker thread
+  // is not desirable, either.
+  nsresult rv = NS_NewPipe2(getter_AddRefs(reader),
+                            getter_AddRefs(writer),
+                            true, true,   // non-blocking
+                            0,            // segment size
+                            UINT32_MAX);  // "infinite" pipe
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  return new CachePushStreamParent(reader, writer);
+}
+
+CachePushStreamParent::~CachePushStreamParent()
+{
+}
+
+already_AddRefed<nsIInputStream>
+CachePushStreamParent::TakeReader()
+{
+  MOZ_ASSERT(mReader);
+  return mReader.forget();
+}
+
+void
+CachePushStreamParent::ActorDestroy(ActorDestroyReason aReason)
+{
+  // If we were gracefully closed we should have gotten RecvClose().  In
+  // that case, the writer will already be closed and this will have no
+  // effect.  This just aborts the writer in the case where the child process
+  // crashes.
+  mWriter->CloseWithStatus(NS_ERROR_ABORT);
+}
+
+bool
+CachePushStreamParent::RecvBuffer(const nsCString& aBuffer)
+{
+  uint32_t numWritten = 0;
+
+  // This should only fail if we hit an OOM condition.
+  nsresult rv = mWriter->Write(aBuffer.get(), aBuffer.Length(), &numWritten);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    RecvClose(rv);
+  }
+
+  return true;
+}
+
+bool
+CachePushStreamParent::RecvClose(const nsresult& aRv)
+{
+  mWriter->CloseWithStatus(aRv);
+  unused << Send__delete__(this);
+  return true;
+}
+
+CachePushStreamParent::CachePushStreamParent(nsIAsyncInputStream* aReader,
+                                             nsIAsyncOutputStream* aWriter)
+  : mReader(aReader)
+  , mWriter(aWriter)
+{
+  MOZ_ASSERT(mReader);
+  MOZ_ASSERT(mWriter);
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/CachePushStreamParent.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_cache_CachePushStreamParent_h
+#define mozilla_dom_cache_CachePushStreamParent_h
+
+#include "mozilla/dom/cache/PCachePushStreamParent.h"
+
+class nsIAsyncInputStream;
+class nsIAsyncOutputStream;
+class nsIInputStream;
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class CachePushStreamParent final : public PCachePushStreamParent
+{
+public:
+  static CachePushStreamParent*
+  Create();
+
+  ~CachePushStreamParent();
+
+  already_AddRefed<nsIInputStream>
+  TakeReader();
+
+private:
+  CachePushStreamParent(nsIAsyncInputStream* aReader,
+                        nsIAsyncOutputStream* aWriter);
+
+  // PCachePushStreamParent methods
+  virtual void
+  ActorDestroy(ActorDestroyReason aReason) override;
+
+  virtual bool
+  RecvBuffer(const nsCString& aBuffer) override;
+
+  virtual bool
+  RecvClose(const nsresult& aRv) override;
+
+  nsCOMPtr<nsIAsyncInputStream> mReader;
+  nsCOMPtr<nsIAsyncOutputStream> mWriter;
+
+  NS_DECL_OWNINGTHREAD
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_CachePushStreamParent_h
--- a/dom/cache/CacheStorage.cpp
+++ b/dom/cache/CacheStorage.cpp
@@ -511,16 +511,23 @@ CacheStorage::GetGlobalObject() const
 #ifdef DEBUG
 void
 CacheStorage::AssertOwningThread() const
 {
   NS_ASSERT_OWNINGTHREAD(CacheStorage);
 }
 #endif
 
+CachePushStreamChild*
+CacheStorage::CreatePushStream(nsIAsyncInputStream* aStream)
+{
+  // This is true because CacheStorage always uses IgnoreBody for requests.
+  MOZ_CRASH("CacheStorage should never create a push stream.");
+}
+
 void
 CacheStorage::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
 {
   // Do nothing.  The Promise will automatically drop the ref to us after
   // calling the callback.  This is what we want as we only registered in order
   // to be held alive via the Promise handle.
 }
 
--- a/dom/cache/CacheStorage.h
+++ b/dom/cache/CacheStorage.h
@@ -39,19 +39,19 @@ namespace workers {
 namespace cache {
 
 class CacheChild;
 class CacheStorageChild;
 class Feature;
 class PCacheResponseOrVoid;
 
 class CacheStorage final : public nsIIPCBackgroundChildCreateCallback
-                             , public nsWrapperCache
-                             , public TypeUtils
-                             , public PromiseNativeHandler
+                         , public nsWrapperCache
+                         , public TypeUtils
+                         , public PromiseNativeHandler
 {
   typedef mozilla::ipc::PBackgroundChild PBackgroundChild;
 
 public:
   static already_AddRefed<CacheStorage>
   CreateOnMainThread(Namespace aNamespace, nsIGlobalObject* aGlobal,
                      nsIPrincipal* aPrincipal, ErrorResult& aRv);
 
@@ -92,16 +92,19 @@ public:
                         const nsTArray<nsString>& aKeys);
 
   // TypeUtils methods
   virtual nsIGlobalObject* GetGlobalObject() const override;
 #ifdef DEBUG
   virtual void AssertOwningThread() const override;
 #endif
 
+  virtual CachePushStreamChild*
+  CreatePushStream(nsIAsyncInputStream* aStream) override;
+
   // PromiseNativeHandler methods
   virtual void
   ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   virtual void
   RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
 private:
--- a/dom/cache/CacheStorageChild.h
+++ b/dom/cache/CacheStorageChild.h
@@ -15,17 +15,17 @@ namespace mozilla {
 namespace dom {
 namespace cache {
 
 class CacheStorage;
 class PCacheChild;
 class Feature;
 
 class CacheStorageChild final : public PCacheStorageChild
-                                  , public ActorChild
+                              , public ActorChild
 {
 public:
   CacheStorageChild(CacheStorage* aListener, Feature* aFeature);
   ~CacheStorageChild();
 
   // Must be called by the associated CacheStorage listener in its
   // ActorDestroy() method.  Also, CacheStorage must Send__delete__() the
   // actor in its destructor to trigger ActorDestroy() if it has not been
@@ -36,16 +36,17 @@ public:
 
   // Synchronously call ActorDestroy on our CacheStorage listener and then start
   // the actor destruction asynchronously from the parent-side.
   virtual void StartDestroy() override;
 
 private:
   // PCacheStorageChild methods
   virtual void ActorDestroy(ActorDestroyReason aReason) override;
+
   virtual bool RecvMatchResponse(const RequestId& aRequestId,
                                  const nsresult& aRv,
                                  const PCacheResponseOrVoid& response) override;
   virtual bool RecvHasResponse(const cache::RequestId& aRequestId,
                                const nsresult& aRv,
                                const bool& aSuccess) override;
   virtual bool RecvOpenResponse(const cache::RequestId& aRequestId,
                                 const nsresult& aRv,
--- a/dom/cache/CacheStorageParent.h
+++ b/dom/cache/CacheStorageParent.h
@@ -18,18 +18,18 @@ template <class T> class nsRefPtr;
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class CacheStreamControlParent;
 class ManagerId;
 
 class CacheStorageParent final : public PCacheStorageParent
-                                   , public PrincipalVerifier::Listener
-                                   , public Manager::Listener
+                               , public PrincipalVerifier::Listener
+                               , public Manager::Listener
 {
 public:
   CacheStorageParent(PBackgroundParent* aManagingActor, Namespace aNamespace,
                      const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
   virtual ~CacheStorageParent();
 
 private:
   // PCacheStorageParent methods
--- a/dom/cache/CacheStreamControlChild.h
+++ b/dom/cache/CacheStreamControlChild.h
@@ -14,18 +14,18 @@
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class ReadStream;
 
 class CacheStreamControlChild final : public PCacheStreamControlChild
-                                        , public StreamControl
-                                        , public ActorChild
+                                    , public StreamControl
+                                    , public ActorChild
 {
 public:
   CacheStreamControlChild();
   ~CacheStreamControlChild();
 
   // ActorChild methods
   virtual void StartDestroy() override;
 
--- a/dom/cache/CacheStreamControlParent.h
+++ b/dom/cache/CacheStreamControlParent.h
@@ -13,18 +13,18 @@
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class ReadStream;
 class StreamList;
 
-class CacheStreamControlParent : public PCacheStreamControlParent
-                               , public StreamControl
+class CacheStreamControlParent final : public PCacheStreamControlParent
+                                     , public StreamControl
 {
 public:
   CacheStreamControlParent();
   ~CacheStreamControlParent();
 
   void SetStreamList(StreamList* aStreamList);
   void Close(const nsID& aId);
   void CloseAll();
--- a/dom/cache/Context.cpp
+++ b/dom/cache/Context.cpp
@@ -315,17 +315,17 @@ Context::QuotaInitRunnable::Run()
 
   return NS_OK;
 }
 
 // Runnable wrapper around Action objects dispatched on the Context.  This
 // runnable executes the Action on the appropriate threads while the Context
 // is initialized.
 class Context::ActionRunnable final : public nsIRunnable
-                                        , public Action::Resolver
+                                    , public Action::Resolver
 {
 public:
   ActionRunnable(Context* aContext, nsIEventTarget* aTarget, Action* aAction,
                  const QuotaInfo& aQuotaInfo)
     : mContext(aContext)
     , mTarget(aTarget)
     , mAction(aAction)
     , mQuotaInfo(aQuotaInfo)
--- a/dom/cache/FetchPut.cpp
+++ b/dom/cache/FetchPut.cpp
@@ -453,11 +453,17 @@ FetchPut::GetGlobalObject() const
 #ifdef DEBUG
 void
 FetchPut::AssertOwningThread() const
 {
   MOZ_ASSERT(mInitiatingThread == NS_GetCurrentThread());
 }
 #endif
 
+CachePushStreamChild*
+FetchPut::CreatePushStream(nsIAsyncInputStream* aStream)
+{
+  MOZ_CRASH("FetchPut should never create a push stream!");
+}
+
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/FetchPut.h
+++ b/dom/cache/FetchPut.h
@@ -25,17 +25,17 @@ namespace mozilla {
 namespace dom {
 
 class Request;
 class Response;
 
 namespace cache {
 
 class FetchPut final : public Manager::Listener
-                         , public TypeUtils
+                     , public TypeUtils
 {
 public:
   typedef std::pair<nsRefPtr<Request>, nsRefPtr<Response>> PutPair;
 
   class Listener
   {
   public:
     virtual void
@@ -89,16 +89,19 @@ private:
   void MaybeNotifyListener();
 
   // TypeUtils methods
   virtual nsIGlobalObject* GetGlobalObject() const override;
 #ifdef DEBUG
   virtual void AssertOwningThread() const override;
 #endif
 
+  virtual CachePushStreamChild*
+  CreatePushStream(nsIAsyncInputStream* aStream) override;
+
   Listener* mListener;
   nsRefPtr<Manager> mManager;
   const RequestId mRequestId;
   const CacheId mCacheId;
   nsCOMPtr<nsIThread> mInitiatingThread;
   nsTArray<State> mStateList;
   uint32_t mPendingCount;
   nsresult mResult;
--- a/dom/cache/PCache.ipdl
+++ b/dom/cache/PCache.ipdl
@@ -1,31 +1,34 @@
 /* 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 protocol PBackground;
+include protocol PCachePushStream;
 include PCacheTypes;
 include protocol PFileDescriptorSet;
 
 include protocol PBlob; // FIXME: bug 792908
 include protocol PCacheStreamControl;
 
 using mozilla::dom::cache::RequestId from "mozilla/dom/cache/Types.h";
 include "mozilla/dom/cache/IPCUtils.h";
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 protocol PCache
 {
   manager PBackground;
+  manages PCachePushStream;
 
 parent:
+  PCachePushStream();
   Teardown();
   Match(RequestId requestId, PCacheRequest request, PCacheQueryParams params);
   MatchAll(RequestId requestId, PCacheRequestOrVoid request, PCacheQueryParams params);
   AddAll(RequestId requestId, PCacheRequest[] requests);
   Put(RequestId requestId, CacheRequestResponse aPut);
   Delete(RequestId requestId, PCacheRequest request, PCacheQueryParams params);
   Keys(RequestId requestId, PCacheRequestOrVoid request, PCacheQueryParams params);
 
new file mode 100644
--- /dev/null
+++ b/dom/cache/PCachePushStream.ipdl
@@ -0,0 +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/. */
+
+include protocol PCache;
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+protocol PCachePushStream
+{
+  manager PCache;
+
+parent:
+  Buffer(nsCString aBuffer);
+  Close(nsresult aRv);
+
+child:
+  // Stream is always destroyed from the parent side.  This occurs if the
+  // parent encounters an error while writing to its pipe or if the child
+  // signals the stream should close by SendClose().
+  __delete__();
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
--- a/dom/cache/PCacheTypes.ipdlh
+++ b/dom/cache/PCacheTypes.ipdlh
@@ -1,12 +1,13 @@
 /* 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 protocol PCachePushStream;
 include protocol PCacheStreamControl;
 include PHeaders;
 include InputStreamParams;
 
 using HeadersGuardEnum from "mozilla/dom/FetchIPCUtils.h";
 using RequestCredentials from "mozilla/dom/FetchIPCUtils.h";
 using RequestMode from "mozilla/dom/FetchIPCUtils.h";
 using mozilla::dom::ResponseType from "mozilla/dom/FetchIPCUtils.h";
@@ -27,16 +28,17 @@ struct PCacheQueryParams
 };
 
 struct PCacheReadStream
 {
   nsID id;
   OptionalInputStreamParams params;
   OptionalFileDescriptorSet fds;
   nullable PCacheStreamControl control;
+  nullable PCachePushStream pushStream;
 };
 
 union PCacheReadStreamOrVoid
 {
   void_t;
   PCacheReadStream;
 };
 
--- a/dom/cache/ReadStream.cpp
+++ b/dom/cache/ReadStream.cpp
@@ -205,16 +205,21 @@ ReadStream::Inner::Serialize(PCacheReadS
 void
 ReadStream::Inner::Serialize(PCacheReadStream* aReadStreamOut)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread);
   MOZ_ASSERT(aReadStreamOut);
   MOZ_ASSERT(mState == Open);
   MOZ_ASSERT(mControl);
 
+  // If we are sending a ReadStream, then we never want to set the
+  // pushStream actors at the same time.
+  aReadStreamOut->pushStreamChild() = nullptr;
+  aReadStreamOut->pushStreamParent() = nullptr;
+
   aReadStreamOut->id() = mId;
   mControl->SerializeControl(aReadStreamOut);
 
   nsAutoTArray<FileDescriptor, 4> fds;
   SerializeInputStream(mStream, aReadStreamOut->params(), fds);
 
   mControl->SerializeFds(aReadStreamOut, fds);
 
@@ -401,16 +406,19 @@ ReadStream::Create(const PCacheReadStrea
 {
   // The parameter may or may not be for a Cache created stream.  The way we
   // tell is by looking at the stream control actor.  If the actor exists,
   // then we know the Cache created it.
   if (!aReadStream.controlChild() && !aReadStream.controlParent()) {
     return nullptr;
   }
 
+  MOZ_ASSERT(!aReadStream.pushStreamChild());
+  MOZ_ASSERT(!aReadStream.pushStreamParent());
+
   // Control is guaranteed to survive this method as ActorDestroy() cannot
   // run on this thread until we complete.
   StreamControl* control;
   if (aReadStream.controlChild()) {
     auto actor = static_cast<CacheStreamControlChild*>(aReadStream.controlChild());
     control = actor;
   } else {
     auto actor = static_cast<CacheStreamControlParent*>(aReadStream.controlParent());
--- a/dom/cache/StreamList.h
+++ b/dom/cache/StreamList.h
@@ -16,17 +16,17 @@ class nsIInputStream;
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class CacheStreamControlParent;
 class Context;
 class Manager;
 
-class StreamList
+class StreamList final
 {
 public:
   StreamList(Manager* aManager, Context* aContext);
 
   void SetStreamControl(CacheStreamControlParent* aStreamControl);
   void RemoveStreamControl(CacheStreamControlParent* aStreamControl);
 
   void Activate(CacheId aCacheId);
--- a/dom/cache/TypeUtils.cpp
+++ b/dom/cache/TypeUtils.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/dom/cache/TypeUtils.h"
 
 #include "mozilla/unused.h"
 #include "mozilla/dom/CacheBinding.h"
 #include "mozilla/dom/InternalRequest.h"
 #include "mozilla/dom/Request.h"
 #include "mozilla/dom/Response.h"
+#include "mozilla/dom/cache/CachePushStreamChild.h"
 #include "mozilla/dom/cache/PCacheTypes.h"
 #include "mozilla/dom/cache/ReadStream.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/FileDescriptorSetChild.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "mozilla/ipc/PFileDescriptorSetChild.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "nsCOMPtr.h"
@@ -24,16 +25,23 @@
 #include "nsIIPCSerializableInputStream.h"
 #include "nsStreamUtils.h"
 #include "nsString.h"
 #include "nsURLParsers.h"
 
 namespace {
 
 using mozilla::ErrorResult;
+using mozilla::unused;
+using mozilla::void_t;
+using mozilla::dom::cache::PCacheReadStream;
+using mozilla::ipc::BackgroundChild;
+using mozilla::ipc::FileDescriptor;
+using mozilla::ipc::PBackgroundChild;
+using mozilla::ipc::PFileDescriptorSetChild;
 
 // Utility function to remove the fragment from a URL, check its scheme, and optionally
 // provide a URL without the query.  We're not using nsIURL or URL to do this because
 // they require going to the main thread.
 static void
 ProcessURL(nsAString& aUrl, bool* aSchemeValidOut,
            nsAString* aUrlWithoutQueryOut, ErrorResult& aRv)
 {
@@ -91,16 +99,41 @@ ProcessURL(nsAString& aUrl, bool* aSchem
   // ParsePath gives us query position relative to the start of the path
   queryPos += pathPos;
 
   // We want everything before the query sine we already removed the trailing
   // fragment
   *aUrlWithoutQueryOut = Substring(aUrl, 0, queryPos - 1);
 }
 
+void
+SerializeNormalStream(nsIInputStream* aStream, PCacheReadStream& aReadStreamOut)
+{
+  nsAutoTArray<FileDescriptor, 4> fds;
+  SerializeInputStream(aStream, aReadStreamOut.params(), fds);
+
+  PFileDescriptorSetChild* fdSet = nullptr;
+  if (!fds.IsEmpty()) {
+    // We should not be serializing until we have an actor ready
+    PBackgroundChild* manager = BackgroundChild::GetForCurrentThread();
+    MOZ_ASSERT(manager);
+
+    fdSet = manager->SendPFileDescriptorSetConstructor(fds[0]);
+    for (uint32_t i = 1; i < fds.Length(); ++i) {
+      unused << fdSet->SendAddFileDescriptor(fds[i]);
+    }
+  }
+
+  if (fdSet) {
+    aReadStreamOut.fds() = fdSet;
+  } else {
+    aReadStreamOut.fds() = void_t();
+  }
+}
+
 } // anonymous namespace
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 using mozilla::ipc::BackgroundChild;
 using mozilla::ipc::FileDescriptor;
@@ -408,71 +441,68 @@ TypeUtils::SerializeCacheStream(nsIInput
                                 PCacheReadStreamOrVoid* aStreamOut,
                                 ErrorResult& aRv)
 {
   *aStreamOut = void_t();
   if (!aStream) {
     return;
   }
 
+  // Option 1: Send a cache-specific ReadStream if we can.
   nsRefPtr<ReadStream> controlled = do_QueryObject(aStream);
   if (controlled) {
     controlled->Serialize(aStreamOut);
     return;
   }
 
-  // TODO: implement CrossProcessPipe if we cannot directly serialize (bug 1110814)
-  nsCOMPtr<nsIIPCSerializableInputStream> serial = do_QueryInterface(aStream);
-  if (!serial) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
   PCacheReadStream readStream;
   readStream.controlChild() = nullptr;
   readStream.controlParent() = nullptr;
-
-  nsAutoTArray<FileDescriptor, 4> fds;
-  SerializeInputStream(aStream, readStream.params(), fds);
-
-  PFileDescriptorSetChild* fdSet = nullptr;
-  if (!fds.IsEmpty()) {
-    // We should not be serializing until we have an actor ready
-    PBackgroundChild* manager = BackgroundChild::GetForCurrentThread();
-    MOZ_ASSERT(manager);
+  readStream.pushStreamChild() = nullptr;
+  readStream.pushStreamParent() = nullptr;
 
-    fdSet = manager->SendPFileDescriptorSetConstructor(fds[0]);
-    for (uint32_t i = 1; i < fds.Length(); ++i) {
-      unused << fdSet->SendAddFileDescriptor(fds[i]);
-    }
-  }
+  // Option 2: Do normal stream serialization if its supported.
+  nsCOMPtr<nsIIPCSerializableInputStream> serial = do_QueryInterface(aStream);
+  if (serial) {
+    SerializeNormalStream(aStream, readStream);
 
-  if (fdSet) {
-    readStream.fds() = fdSet;
+  // Option 3: As a last resort push data across manually.  Should only be
+  //           needed for nsPipe input stream.  Only works for async,
+  //           non-blocking streams.
   } else {
-    readStream.fds() = void_t();
+    SerializePushStream(aStream, readStream, aRv);
+    if (NS_WARN_IF(aRv.Failed())) { return; }
   }
 
   *aStreamOut = readStream;
 }
 
-nsIThread*
-TypeUtils::GetStreamThread()
+void
+TypeUtils::SerializePushStream(nsIInputStream* aStream,
+                               PCacheReadStream& aReadStreamOut,
+                               ErrorResult& aRv)
 {
-  AssertOwningThread();
-
-  if (!mStreamThread) {
-    // Named threads only allow 16 bytes for their names.  Try to make
-    // it meaningful...
-    // TODO: use a thread pool or singleton thread here (bug 1119864)
-    nsresult rv = NS_NewNamedThread("DOMCacheTypeU",
-                                    getter_AddRefs(mStreamThread));
-    if (NS_FAILED(rv) || !mStreamThread) {
-      MOZ_CRASH("Failed to create DOM Cache serialization thread.");
-    }
+  nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aStream);
+  if (NS_WARN_IF(!asyncStream)) {
+    aRv = NS_ERROR_FAILURE;
+    return;
   }
 
-  return mStreamThread;
+  bool nonBlocking = false;
+  aRv = asyncStream->IsNonBlocking(&nonBlocking);
+  if (NS_WARN_IF(aRv.Failed())) { return; }
+  if (NS_WARN_IF(!nonBlocking)) {
+    aRv = NS_ERROR_FAILURE;
+    return;
+  }
+
+  aReadStreamOut.pushStreamChild() = CreatePushStream(asyncStream);
+  MOZ_ASSERT(aReadStreamOut.pushStreamChild());
+  aReadStreamOut.params() = void_t();
+  aReadStreamOut.fds() = void_t();
+
+  // CachePushStreamChild::Start() must be called after sending the stream
+  // across to the parent side.
 }
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/TypeUtils.h
+++ b/dom/cache/TypeUtils.h
@@ -4,36 +4,38 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_cache_TypesUtils_h
 #define mozilla_dom_cache_TypesUtils_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/BindingUtils.h"
-#include "nsCOMPtr.h"
 #include "nsError.h"
 
 class nsIGlobalObject;
+class nsIAsyncInputStream;
 class nsIInputStream;
 
 namespace mozilla {
 namespace dom {
 
 struct CacheQueryOptions;
 class InternalRequest;
 class InternalResponse;
 class OwningRequestOrUSVString;
 class Request;
 class RequestOrUSVString;
 class Response;
 
 namespace cache {
 
+class CachePushStreamChild;
 class PCacheQueryParams;
+class PCacheReadStream;
 class PCacheReadStreamOrVoid;
 class PCacheRequest;
 class PCacheResponse;
 
 class TypeUtils
 {
 public:
   enum BodyAction
@@ -58,16 +60,19 @@ public:
   ~TypeUtils() { }
   virtual nsIGlobalObject* GetGlobalObject() const = 0;
 #ifdef DEBUG
   virtual void AssertOwningThread() const = 0;
 #else
   inline void AssertOwningThread() const { }
 #endif
 
+  virtual CachePushStreamChild*
+  CreatePushStream(nsIAsyncInputStream* aStream) = 0;
+
   already_AddRefed<InternalRequest>
   ToInternalRequest(const RequestOrUSVString& aIn, BodyAction aBodyAction,
                     ErrorResult& aRv);
 
   already_AddRefed<InternalRequest>
   ToInternalRequest(const OwningRequestOrUSVString& aIn, BodyAction aBodyAction,
                     ErrorResult& aRv);
 
@@ -102,18 +107,18 @@ private:
 
   already_AddRefed<InternalRequest>
   ToInternalRequest(const nsAString& aIn, ErrorResult& aRv);
 
   void
   SerializeCacheStream(nsIInputStream* aStream, PCacheReadStreamOrVoid* aStreamOut,
                        ErrorResult& aRv);
 
-  nsIThread* GetStreamThread();
-
-  nsCOMPtr<nsIThread> mStreamThread;
+  void
+  SerializePushStream(nsIInputStream* aStream, PCacheReadStream& aReadStreamOut,
+                      ErrorResult& aRv);
 };
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_cache_TypesUtils_h
--- a/dom/cache/moz.build
+++ b/dom/cache/moz.build
@@ -7,16 +7,18 @@
 EXPORTS.mozilla.dom.cache += [
     'Action.h',
     'ActorChild.h',
     'ActorUtils.h',
     'AutoUtils.h',
     'Cache.h',
     'CacheChild.h',
     'CacheParent.h',
+    'CachePushStreamChild.h',
+    'CachePushStreamParent.h',
     'CacheStorage.h',
     'CacheStorageChild.h',
     'CacheStorageParent.h',
     'CacheStreamControlChild.h',
     'CacheStreamControlParent.h',
     'Context.h',
     'DBAction.h',
     'DBSchema.h',
@@ -39,16 +41,18 @@ EXPORTS.mozilla.dom.cache += [
 
 UNIFIED_SOURCES += [
     'Action.cpp',
     'ActorChild.cpp',
     'AutoUtils.cpp',
     'Cache.cpp',
     'CacheChild.cpp',
     'CacheParent.cpp',
+    'CachePushStreamChild.cpp',
+    'CachePushStreamParent.cpp',
     'CacheStorage.cpp',
     'CacheStorageChild.cpp',
     'CacheStorageParent.cpp',
     'CacheStreamControlChild.cpp',
     'CacheStreamControlParent.cpp',
     'Context.cpp',
     'DBAction.cpp',
     'DBSchema.cpp',
@@ -64,16 +68,17 @@ UNIFIED_SOURCES += [
     'StreamList.cpp',
     'StreamUtils.cpp',
     'TypeUtils.cpp',
 ]
 
 IPDL_SOURCES += [
     'CacheInitData.ipdlh',
     'PCache.ipdl',
+    'PCachePushStream.ipdl',
     'PCacheStorage.ipdl',
     'PCacheStreamControl.ipdl',
     'PCacheTypes.ipdlh',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
--- a/dom/cache/test/mochitest/mochitest.ini
+++ b/dom/cache/test/mochitest/mochitest.ini
@@ -11,17 +11,19 @@ support-files =
   test_cache_match_request.js
   test_cache_matchAll_request.js
   test_cache_overwrite.js
   mirror.sjs
   test_cache_match_vary.js
   vary.sjs
   test_caches.js
   test_cache_keys.js
+  test_cache_put.js
 
 [test_cache.html]
 [test_cache_add.html]
 [test_cache_match_request.html]
 [test_cache_matchAll_request.html]
 [test_cache_overwrite.html]
 [test_cache_match_vary.html]
 [test_caches.html]
 [test_cache_keys.html]
+[test_cache_put.html]
new file mode 100644
--- /dev/null
+++ b/dom/cache/test/mochitest/test_cache_put.html
@@ -0,0 +1,20 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Validate Interfaces Exposed to Workers</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="driver.js"></script>
+</head>
+<body>
+<iframe id="frame"></iframe>
+<script class="testbody" type="text/javascript">
+  runTests("test_cache_put.js")
+    .then(function() {
+      SimpleTest.finish();
+    });
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/cache/test/mochitest/test_cache_put.js
@@ -0,0 +1,26 @@
+var url = 'test_cache.js';
+var cache;
+var fetchResponse;
+Promise.all([fetch(url),
+             caches.open('putter' + context)]).then(function(results) {
+  fetchResponse = results[0];
+  cache = results[1];
+  return cache.put(url, fetchResponse.clone());
+}).then(function(result) {
+  is(undefined, result, 'Successful put() should resolve undefined');
+  return cache.match(url);
+}).then(function(response) {
+  ok(response, 'match() should find resppnse that was previously put()');
+  ok(response.url.endsWith(url), 'matched response should match original url');
+  return Promise.all([fetchResponse.text(),
+                      response.text()]);
+}).then(function(results) {
+  // suppress large assert spam unless its relevent
+  if (results[0] !== results[1]) {
+    is(results[0], results[1], 'stored response body should match original');
+  }
+  return caches.delete('putter' + context);
+}).then(function(deleted) {
+  ok(deleted, "The cache should be deleted successfully");
+  testDone();
+});
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -4213,16 +4213,23 @@ CanvasRenderingContext2D::DrawImage(cons
       gfxPlatform::GetPlatform()->GetSkiaGLGlue()) {
     mozilla::gl::GLContext* gl = gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext();
 
     HTMLVideoElement* video = &image.GetAsHTMLVideoElement();
     if (!video) {
       return;
     }
 
+#ifdef MOZ_EME
+    if (video->ContainsRestrictedContent()) {
+      error.Throw(NS_ERROR_NOT_AVAILABLE);
+      return;
+    }
+#endif
+
     uint16_t readyState;
     if (NS_SUCCEEDED(video->GetReadyState(&readyState)) &&
         readyState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) {
       // still loading, just return
       return;
     }
 
     // If it doesn't have a principal, just bail
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -666,24 +666,16 @@ FetchDriver::OnStartRequest(nsIRequest* 
   nsRefPtr<InternalResponse> response = new InternalResponse(responseStatus, statusText);
 
   nsRefPtr<FillResponseHeaders> visitor = new FillResponseHeaders(response);
   rv = channel->VisitResponseHeaders(visitor);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     NS_WARNING("Failed to visit all headers.");
   }
 
-  mResponse = BeginAndGetFilteredResponse(response);
-
-  nsCOMPtr<nsISupports> securityInfo;
-  rv = channel->GetSecurityInfo(getter_AddRefs(securityInfo));
-  if (securityInfo) {
-    mResponse->SetSecurityInfo(securityInfo);
-  }
-
   // We open a pipe so that we can immediately set the pipe's read end as the
   // response's body. Setting the segment size to UINT32_MAX means that the
   // pipe has infinite space. The nsIChannel will continue to buffer data in
   // xpcom events even if we block on a fixed size pipe.  It might be possible
   // to suspend the channel and then resume when there is space available, but
   // for now use an infinite pipe to avoid blocking.
   nsCOMPtr<nsIInputStream> pipeInputStream;
   rv = NS_NewPipe(getter_AddRefs(pipeInputStream),
@@ -692,18 +684,27 @@ FetchDriver::OnStartRequest(nsIRequest* 
                   UINT32_MAX /* infinite pipe */,
                   true /* non-blocking input, otherwise you deadlock */,
                   false /* blocking output, since the pipe is 'in'finite */ );
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailWithNetworkError();
     // Cancel request.
     return rv;
   }
+  response->SetBody(pipeInputStream);
 
-  mResponse->SetBody(pipeInputStream);
+  nsCOMPtr<nsISupports> securityInfo;
+  rv = channel->GetSecurityInfo(getter_AddRefs(securityInfo));
+  if (securityInfo) {
+    response->SetSecurityInfo(securityInfo);
+  }
+
+  // Resolves fetch() promise which may trigger code running in a worker.  Make
+  // sure the Response is fully initialized before calling this.
+  mResponse = BeginAndGetFilteredResponse(response);
 
   nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailWithNetworkError();
     // Cancel request.
     return rv;
   }
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -3061,17 +3061,17 @@ void HTMLMediaElement::ProcessMediaFragm
     mFragmentStart = parser.GetStartTime();
   }
 }
 
 void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
                                       nsAutoPtr<const MetadataTags> aTags)
 {
   mMediaInfo = *aInfo;
-  mIsEncrypted = aInfo->mIsEncrypted;
+  mIsEncrypted = aInfo->IsEncrypted();
   mTags = aTags.forget();
   mLoadedDataFired = false;
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
 
   if (mIsEncrypted) {
     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
     obs->NotifyObservers(static_cast<nsIContent*>(this), "media-eme-metadataloaded", nullptr);
   }
@@ -3081,16 +3081,26 @@ void HTMLMediaElement::MetadataLoaded(co
     mMediaSize = aInfo->mVideo.mDisplay;
     DispatchAsyncEvent(NS_LITERAL_STRING("resize"));
   }
   DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
   if (mDecoder && mDecoder->IsTransportSeekable() && mDecoder->IsMediaSeekable()) {
     ProcessMediaFragmentURI();
     mDecoder->SetFragmentEndTime(mFragmentEnd);
   }
+  if (mIsEncrypted) {
+    if (!mMediaSource) {
+      DecodeError();
+      return;
+    }
+
+#ifdef MOZ_EME
+    DispatchEncrypted(aInfo->mCrypto.mInitData, aInfo->mCrypto.mType);
+#endif
+  }
 
   // Expose the tracks to JS directly.
   for (OutputMediaStream& out : mOutputStreams) {
     if (aInfo->HasAudio()) {
       TrackID audioTrackId = aInfo->mAudio.mTrackInfo.mOutputId;
       out.mStream->CreateDOMTrack(audioTrackId, MediaSegment::AUDIO);
     }
     if (aInfo->HasVideo()) {
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -566,16 +566,21 @@ child:
      * @see nsIDOMWindowUtils sendKeyEvent.
      */
     KeyEvent(nsString aType,
              int32_t aKeyCode,
              int32_t aCharCode,
              int32_t aModifiers,
              bool aPreventDefault);
 
+    /**
+     * APZ notification for mouse scroll testing events.
+     */
+    MouseScrollTestEvent(ViewID aScrollId, nsString aEvent);
+
     CompositionEvent(WidgetCompositionEvent event);
 
     SelectionEvent(WidgetSelectionEvent event);
 
     /**
      * Activate event forwarding from client to parent.
      */
     ActivateFrameEvent(nsString aType, bool capture);
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2194,16 +2194,23 @@ TabChild::RecvMouseWheelEvent(const Widg
   APZCCallbackHelper::DispatchWidgetEvent(event);
 
   if (gfxPrefs::AsyncPanZoomEnabled()) {
     mAPZEventState->ProcessWheelEvent(event, aGuid, aInputBlockId);
   }
   return true;
 }
 
+bool
+TabChild::RecvMouseScrollTestEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
+{
+  APZCCallbackHelper::NotifyMozMouseScrollEvent(aScrollId, aEvent);
+  return true;
+}
+
 static Touch*
 GetTouchForIdentifier(const WidgetTouchEvent& aEvent, int32_t aId)
 {
   for (uint32_t i = 0; i < aEvent.touches.Length(); ++i) {
     Touch* touch = static_cast<Touch*>(aEvent.touches[i].get());
     if (touch->mIdentifier == aId) {
       return touch;
     }
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -360,16 +360,18 @@ public:
     virtual bool RecvRealTouchMoveEvent(const WidgetTouchEvent& aEvent,
                                         const ScrollableLayerGuid& aGuid,
                                         const uint64_t& aInputBlockId) override;
     virtual bool RecvKeyEvent(const nsString& aType,
                               const int32_t&  aKeyCode,
                               const int32_t&  aCharCode,
                               const int32_t&  aModifiers,
                               const bool&     aPreventDefault) override;
+    virtual bool RecvMouseScrollTestEvent(const FrameMetrics::ViewID& aScrollId,
+                                          const nsString& aEvent) override;
     virtual bool RecvCompositionEvent(const mozilla::WidgetCompositionEvent& event) override;
     virtual bool RecvSelectionEvent(const mozilla::WidgetSelectionEvent& event) override;
     virtual bool RecvActivateFrameEvent(const nsString& aType, const bool& capture) override;
     virtual bool RecvLoadRemoteScript(const nsString& aURL,
                                       const bool& aRunInGlobalScope) override;
     virtual bool RecvAsyncMessage(const nsString& aMessage,
                                   const ClonedMessageData& aData,
                                   InfallibleTArray<CpowEntry>&& aCpows,
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -969,16 +969,24 @@ void TabParent::NotifyAPZStateChange(Vie
                                      int aArg)
 {
   if (!mIsDestroyed) {
     unused << SendNotifyAPZStateChange(aViewId, aChange, aArg);
   }
 }
 
 void
+TabParent::NotifyMouseScrollTestEvent(const ViewID& aScrollId, const nsString& aEvent)
+{
+  if (!mIsDestroyed) {
+    unused << SendMouseScrollTestEvent(aScrollId, aEvent);
+  }
+}
+
+void
 TabParent::Activate()
 {
   if (!mIsDestroyed) {
     unused << SendActivate();
   }
 }
 
 void
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -237,16 +237,17 @@ public:
                          const ScrollableLayerGuid& aGuid);
     void HandleLongTap(const CSSPoint& aPoint,
                        Modifiers aModifiers,
                        const ScrollableLayerGuid& aGuid,
                        uint64_t aInputBlockId);
     void NotifyAPZStateChange(ViewID aViewId,
                               APZStateChange aChange,
                               int aArg);
+    void NotifyMouseScrollTestEvent(const ViewID& aScrollId, const nsString& aEvent);
     void Activate();
     void Deactivate();
 
     bool MapEventCoordinatesForChildProcess(mozilla::WidgetEvent* aEvent);
     void MapEventCoordinatesForChildProcess(const LayoutDeviceIntPoint& aOffset,
                                             mozilla::WidgetEvent* aEvent);
     LayoutDeviceToCSSScale GetLayoutDeviceToCSSScale();
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1277,16 +1277,17 @@ MediaDecoderOwner::NextFrameStatus Media
   }
   return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
 }
 
 static const char* const gMachineStateStr[] = {
   "NONE",
   "DECODING_METADATA",
   "WAIT_FOR_RESOURCES",
+  "WAIT_FOR_CDM",
   "DECODING_FIRSTFRAME",
   "DORMANT",
   "DECODING",
   "SEEKING",
   "BUFFERING",
   "COMPLETED",
   "SHUTDOWN"
 };
@@ -1577,23 +1578,29 @@ void MediaDecoderStateMachine::NotifyWai
       &MediaDecoderStateMachine::DoNotifyWaitingForResourcesStatusChanged));
   DecodeTaskQueue()->Dispatch(task);
 }
 
 void MediaDecoderStateMachine::DoNotifyWaitingForResourcesStatusChanged()
 {
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  if (mState != DECODER_STATE_WAIT_FOR_RESOURCES) {
-    return;
-  }
+
   DECODER_LOG("DoNotifyWaitingForResourcesStatusChanged");
-  // The reader is no longer waiting for resources (say a hardware decoder),
-  // we can now proceed to decode metadata.
-  SetState(DECODER_STATE_DECODING_NONE);
+
+  if (mState == DECODER_STATE_WAIT_FOR_RESOURCES) {
+    // The reader is no longer waiting for resources (say a hardware decoder),
+    // we can now proceed to decode metadata.
+    SetState(DECODER_STATE_DECODING_NONE);
+  } else if (mState == DECODER_STATE_WAIT_FOR_CDM &&
+             !mReader->IsWaitingOnCDMResource()) {
+    SetState(DECODER_STATE_DECODING_FIRSTFRAME);
+    EnqueueDecodeFirstFrameTask();
+  }
+
   ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::PlayInternal()
 {
   NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
@@ -2264,16 +2271,23 @@ nsresult MediaDecoderStateMachine::Decod
 
   if (mGotDurationFromMetaData) {
     // We have all the information required: duration and size
     // Inform the element that we've loaded the metadata.
     EnqueueLoadedMetadataEvent();
   }
 
   if (mState == DECODER_STATE_DECODING_METADATA) {
+    if (mReader->IsWaitingOnCDMResource()) {
+      // Metadata parsing was successful but we're still waiting for CDM caps
+      // to become available so that we can build the correct decryptor/decoder.
+      SetState(DECODER_STATE_WAIT_FOR_CDM);
+      return NS_OK;
+    }
+
     SetState(DECODER_STATE_DECODING_FIRSTFRAME);
     res = EnqueueDecodeFirstFrameTask();
     if (NS_FAILED(res)) {
       return NS_ERROR_FAILURE;
     }
   }
   ScheduleStateMachine();
 
@@ -2673,16 +2687,17 @@ nsresult MediaDecoderStateMachine::RunSt
       DECODER_LOG("Shutdown started");
       return NS_OK;
     }
 
     case DECODER_STATE_DORMANT: {
       return NS_OK;
     }
 
+    case DECODER_STATE_WAIT_FOR_CDM:
     case DECODER_STATE_WAIT_FOR_RESOURCES: {
       return NS_OK;
     }
 
     case DECODER_STATE_DECODING_NONE: {
       SetState(DECODER_STATE_DECODING_METADATA);
       // Ensure we have a decode thread to decode metadata.
       return EnqueueDecodeMetadataTask();
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -134,16 +134,17 @@ public:
 
   nsresult Init(MediaDecoderStateMachine* aCloneDonor);
 
   // Enumeration for the valid decoding states
   enum State {
     DECODER_STATE_DECODING_NONE,
     DECODER_STATE_DECODING_METADATA,
     DECODER_STATE_WAIT_FOR_RESOURCES,
+    DECODER_STATE_WAIT_FOR_CDM,
     DECODER_STATE_DECODING_FIRSTFRAME,
     DECODER_STATE_DORMANT,
     DECODER_STATE_DECODING,
     DECODER_STATE_SEEKING,
     DECODER_STATE_BUFFERING,
     DECODER_STATE_COMPLETED,
     DECODER_STATE_SHUTDOWN
   };
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #if !defined(MediaInfo_h)
 #define MediaInfo_h
 
 #include "nsSize.h"
 #include "nsRect.h"
 #include "ImageTypes.h"
 #include "nsString.h"
+#include "nsTArray.h"
 #include "StreamBuffer.h" // for TrackID
 
 namespace mozilla {
 
 struct TrackInfo {
   void Init(const nsAString& aId,
             const nsAString& aKind,
             const nsAString& aLabel,
@@ -97,37 +98,54 @@ public:
   uint32_t mChannels;
 
   // True if we have an active audio bitstream.
   bool mHasAudio;
 
   TrackInfo mTrackInfo;
 };
 
+class EncryptionInfo {
+public:
+  EncryptionInfo() : mIsEncrypted(false) {}
+
+  // Encryption type to be passed to JS. Usually `cenc'.
+  nsString mType;
+
+  // Encryption data.
+  nsTArray<uint8_t> mInitData;
+
+  // True if the stream has encryption metadata
+  bool mIsEncrypted;
+};
+
 class MediaInfo {
 public:
-  MediaInfo() : mIsEncrypted(false) {}
-
   bool HasVideo() const
   {
     return mVideo.mHasVideo;
   }
 
   bool HasAudio() const
   {
     return mAudio.mHasAudio;
   }
 
+  bool IsEncrypted() const
+  {
+    return mCrypto.mIsEncrypted;
+  }
+
   bool HasValidMedia() const
   {
     return HasVideo() || HasAudio();
   }
 
-  bool mIsEncrypted;
-
   // TODO: Store VideoInfo and AudioIndo in arrays to support multi-tracks.
   VideoInfo mVideo;
   AudioInfo mAudio;
+
+  EncryptionInfo mCrypto;
 };
 
 } // namespace mozilla
 
 #endif // MediaInfo_h
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -2208,16 +2208,17 @@ MediaStream::RunAfterPendingUpdates(alre
   MOZ_ASSERT(NS_IsMainThread());
   MediaStreamGraphImpl* graph = GraphImpl();
   nsCOMPtr<nsIRunnable> runnable(aRunnable);
 
   // Special case when a non-realtime graph has not started, to ensure the
   // runnable will run in finite time.
   if (!(graph->mRealtime || graph->mNonRealtimeProcessing)) {
     runnable->Run();
+    return;
   }
 
   class Message : public ControlMessage {
   public:
     explicit Message(MediaStream* aStream,
                      already_AddRefed<nsIRunnable> aRunnable)
       : ControlMessage(aStream)
       , mRunnable(aRunnable) {}
--- a/dom/media/fmp4/AVCCDecoderModule.cpp
+++ b/dom/media/fmp4/AVCCDecoderModule.cpp
@@ -260,22 +260,16 @@ AVCCDecoderModule::~AVCCDecoderModule()
 }
 
 nsresult
 AVCCDecoderModule::Startup()
 {
   return mPDM->Startup();
 }
 
-nsresult
-AVCCDecoderModule::Shutdown()
-{
-  return mPDM->Shutdown();
-}
-
 already_AddRefed<MediaDataDecoder>
 AVCCDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                                       layers::LayersBackend aLayersBackend,
                                       layers::ImageContainer* aImageContainer,
                                       FlushableMediaTaskQueue* aVideoTaskQueue,
                                       MediaDataDecoderCallback* aCallback)
 {
   nsRefPtr<MediaDataDecoder> decoder;
--- a/dom/media/fmp4/AVCCDecoderModule.h
+++ b/dom/media/fmp4/AVCCDecoderModule.h
@@ -23,17 +23,16 @@ class AVCCMediaDataDecoder;
 // AVCC-only decoder modules are AppleVideoDecoder and EMEH264Decoder.
 
 class AVCCDecoderModule : public PlatformDecoderModule {
 public:
   explicit AVCCDecoderModule(PlatformDecoderModule* aPDM);
   virtual ~AVCCDecoderModule();
 
   virtual nsresult Startup() override;
-  virtual nsresult Shutdown() override;
 
   virtual already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
                      FlushableMediaTaskQueue* aVideoTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
--- a/dom/media/fmp4/BlankDecoderModule.cpp
+++ b/dom/media/fmp4/BlankDecoderModule.cpp
@@ -201,21 +201,16 @@ private:
   int64_t mFrameSum;
   uint32_t mChannelCount;
   uint32_t mSampleRate;
 };
 
 class BlankDecoderModule : public PlatformDecoderModule {
 public:
 
-  // Called when the decoders have shutdown. Main thread only.
-  virtual nsresult Shutdown() override {
-    return NS_OK;
-  }
-
   // Decode thread.
   virtual already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
                      FlushableMediaTaskQueue* aVideoTaskQueue,
                      MediaDataDecoderCallback* aCallback) override {
     BlankVideoDataCreator* creator = new BlankVideoDataCreator(
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -151,16 +151,17 @@ MP4Reader::MP4Reader(AbstractMediaDecode
   : MediaDecoderReader(aDecoder)
   , mAudio(MediaData::AUDIO_DATA, Preferences::GetUint("media.mp4-audio-decode-ahead", 2))
   , mVideo(MediaData::VIDEO_DATA, Preferences::GetUint("media.mp4-video-decode-ahead", 2))
   , mLastReportedNumDecodedFrames(0)
   , mLayersBackendType(layers::LayersBackend::LAYERS_NONE)
   , mDemuxerInitialized(false)
   , mFoundSPSForTelemetry(false)
   , mIsEncrypted(false)
+  , mAreDecodersSetup(false)
   , mIndexReady(false)
   , mDemuxerMonitor("MP4 Demuxer")
 #if defined(MP4_READER_DORMANT_HEURISTIC)
   , mDormantEnabled(Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false))
 #endif
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
   MOZ_COUNT_CTOR(MP4Reader);
@@ -199,20 +200,17 @@ MP4Reader::Shutdown()
     mVideo.mTaskQueue->AwaitShutdownAndIdle();
     mVideo.mTaskQueue = nullptr;
   }
   mVideo.mPromise.SetMonitor(nullptr);
   MOZ_ASSERT(mVideo.mPromise.IsEmpty());
   // Dispose of the queued sample before shutting down the demuxer
   mQueuedVideoSample = nullptr;
 
-  if (mPlatform) {
-    mPlatform->Shutdown();
-    mPlatform = nullptr;
-  }
+  mPlatform = nullptr;
 
   return MediaDecoderReader::Shutdown();
 }
 
 void
 MP4Reader::InitLayersBackendType()
 {
   if (!IsVideoContentType(mDecoder->GetResource()->GetContentType())) {
@@ -261,51 +259,23 @@ MP4Reader::Init(MediaDecoderReader* aClo
     sSetupPrefCache = true;
     Preferences::AddBoolVarCache(&sIsEMEEnabled, "media.eme.enabled", false);
     Preferences::AddBoolVarCache(&sDemuxSkipToNextKeyframe, "media.fmp4.demux-skip", true);
   }
 
   return NS_OK;
 }
 
-#ifdef MOZ_EME
-class DispatchKeyNeededEvent : public nsRunnable {
-public:
-  DispatchKeyNeededEvent(AbstractMediaDecoder* aDecoder,
-                         nsTArray<uint8_t>& aInitData,
-                         const nsString& aInitDataType)
-    : mDecoder(aDecoder)
-    , mInitData(aInitData)
-    , mInitDataType(aInitDataType)
-  {
-  }
-  NS_IMETHOD Run() {
-    // Note: Null check the owner, as the decoder could have been shutdown
-    // since this event was dispatched.
-    MediaDecoderOwner* owner = mDecoder->GetOwner();
-    if (owner) {
-      owner->DispatchEncrypted(mInitData, mInitDataType);
-    }
-    mDecoder = nullptr;
-    return NS_OK;
-  }
-private:
-  nsRefPtr<AbstractMediaDecoder> mDecoder;
-  nsTArray<uint8_t> mInitData;
-  nsString mInitDataType;
-};
-#endif
-
 void MP4Reader::RequestCodecResource() {
   if (mVideo.mDecoder) {
     mVideo.mDecoder->AllocateMediaResources();
   }
 }
 
-bool MP4Reader::IsWaitingOnCodecResource() {
+bool MP4Reader::IsWaitingMediaResources() {
   return mVideo.mDecoder && mVideo.mDecoder->IsWaitingMediaResources();
 }
 
 bool MP4Reader::IsWaitingOnCDMResource() {
 #ifdef MOZ_EME
   nsRefPtr<CDMProxy> proxy;
   {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
@@ -325,25 +295,16 @@ bool MP4Reader::IsWaitingOnCDMResource()
     LOG("capsKnown=%d", caps.AreCapsKnown());
     return !caps.AreCapsKnown();
   }
 #else
   return false;
 #endif
 }
 
-bool MP4Reader::IsWaitingMediaResources()
-{
-  // IsWaitingOnCDMResource() *must* come first, because we don't know whether
-  // we can create a decoder until the CDM is initialized and it has told us
-  // whether *it* will decode, or whether we need to create a PDM to do the
-  // decoding
-  return IsWaitingOnCDMResource() || IsWaitingOnCodecResource();
-}
-
 void
 MP4Reader::ExtractCryptoInitData(nsTArray<uint8_t>& aInitData)
 {
   MOZ_ASSERT(mCrypto.valid);
   const nsTArray<mp4_demuxer::PsshInfo>& psshs = mCrypto.pssh;
   for (uint32_t i = 0; i < psshs.Length(); i++) {
     aInitData.AppendElements(psshs[i].data);
   }
@@ -402,140 +363,163 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo
     if (mAudio.mActive) {
       mAudio.mTrackDemuxer = new MP4AudioDemuxer(mDemuxer);
     }
     mCrypto = mDemuxer->Crypto();
 
     {
       MonitorAutoUnlock unlock(mDemuxerMonitor);
       ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-      mInfo.mIsEncrypted = mIsEncrypted = mCrypto.valid;
+      mInfo.mCrypto.mIsEncrypted = mIsEncrypted = mCrypto.valid;
     }
 
     // Remember that we've initialized the demuxer, so that if we're decoding
     // an encrypted stream and we need to wait for a CDM to be set, we don't
     // need to reinit the demuxer.
     mDemuxerInitialized = true;
   } else if (mPlatform && !IsWaitingMediaResources()) {
     *aInfo = mInfo;
     *aTags = nullptr;
     return NS_OK;
   }
 
-  if (mCrypto.valid) {
-#ifdef MOZ_EME
-    // We have encrypted audio or video. We'll need a CDM to decrypt and
-    // possibly decode this. Wait until we've received a CDM from the
-    // JavaScript player app. Note: we still go through the motions here
-    // even if EME is disabled, so that if script tries and fails to create
-    // a CDM, we can detect that and notify chrome and show some UI explaining
-    // that we failed due to EME being disabled.
-    nsRefPtr<CDMProxy> proxy;
+  if (HasAudio()) {
+    const AudioDecoderConfig& audio = mDemuxer->AudioConfig();
+    mInfo.mAudio.mRate = audio.samples_per_second;
+    mInfo.mAudio.mChannels = audio.channel_count;
+    mAudio.mCallback = new DecoderCallback(this, kAudio);
+  }
+
+  if (HasVideo()) {
+    const VideoDecoderConfig& video = mDemuxer->VideoConfig();
+    mInfo.mVideo.mDisplay =
+      nsIntSize(video.display_width, video.display_height);
+    mVideo.mCallback = new DecoderCallback(this, kVideo);
+
+    // Collect telemetry from h264 AVCC SPS.
+    if (!mFoundSPSForTelemetry) {
+      mFoundSPSForTelemetry = AccumulateSPSTelemetry(video.extra_data);
+    }
+  }
+
+  if (mIsEncrypted) {
     nsTArray<uint8_t> initData;
     ExtractCryptoInitData(initData);
     if (initData.Length() == 0) {
       return NS_ERROR_FAILURE;
     }
-    if (!mInitDataEncountered.Contains(initData)) {
-      mInitDataEncountered.AppendElement(initData);
-      NS_DispatchToMainThread(new DispatchKeyNeededEvent(mDecoder, initData, NS_LITERAL_STRING("cenc")));
-    }
-    if (IsWaitingMediaResources()) {
-      return NS_OK;
-    }
-    MOZ_ASSERT(!IsWaitingMediaResources());
 
-    {
-      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-      proxy = mDecoder->GetCDMProxy();
-    }
-    MOZ_ASSERT(proxy);
-
-    mPlatform = PlatformDecoderModule::CreateCDMWrapper(proxy,
-                                                        HasAudio(),
-                                                        HasVideo());
-    NS_ENSURE_TRUE(mPlatform, NS_ERROR_FAILURE);
-#else
-    // EME not supported.
-    return NS_ERROR_FAILURE;
-#endif
-  } else {
-    mPlatform = PlatformDecoderModule::Create();
-    NS_ENSURE_TRUE(mPlatform, NS_ERROR_FAILURE);
-  }
-
-  if (HasAudio()) {
-    const AudioDecoderConfig& audio = mDemuxer->AudioConfig();
-    if (mInfo.mAudio.mHasAudio && !IsSupportedAudioMimeType(audio.mime_type)) {
-      return NS_ERROR_FAILURE;
-    }
-    mInfo.mAudio.mRate = audio.samples_per_second;
-    mInfo.mAudio.mChannels = audio.channel_count;
-    mAudio.mCallback = new DecoderCallback(this, kAudio);
-    mAudio.mDecoder = mPlatform->CreateAudioDecoder(audio,
-                                                    mAudio.mTaskQueue,
-                                                    mAudio.mCallback);
-    NS_ENSURE_TRUE(mAudio.mDecoder != nullptr, NS_ERROR_FAILURE);
-    nsresult rv = mAudio.mDecoder->Init();
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  if (HasVideo()) {
-    const VideoDecoderConfig& video = mDemuxer->VideoConfig();
-    if (mInfo.mVideo.mHasVideo && !IsSupportedVideoMimeType(video.mime_type)) {
-      return NS_ERROR_FAILURE;
-    }
-    mInfo.mVideo.mDisplay =
-      nsIntSize(video.display_width, video.display_height);
-    mVideo.mCallback = new DecoderCallback(this, kVideo);
-    if (mSharedDecoderManager && mPlatform->SupportsSharedDecoders(video)) {
-      mVideo.mDecoder =
-        mSharedDecoderManager->CreateVideoDecoder(mPlatform,
-                                                  video,
-                                                  mLayersBackendType,
-                                                  mDecoder->GetImageContainer(),
-                                                  mVideo.mTaskQueue,
-                                                  mVideo.mCallback);
-    } else {
-      mVideo.mDecoder = mPlatform->CreateVideoDecoder(video,
-                                                      mLayersBackendType,
-                                                      mDecoder->GetImageContainer(),
-                                                      mVideo.mTaskQueue,
-                                                      mVideo.mCallback);
-    }
-    NS_ENSURE_TRUE(mVideo.mDecoder != nullptr, NS_ERROR_FAILURE);
-    nsresult rv = mVideo.mDecoder->Init();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // Collect telemetry from h264 AVCC SPS.
-    if (!mFoundSPSForTelemetry) {
-      mFoundSPSForTelemetry = AccumulateSPSTelemetry(video.extra_data);
-    }
+    mInfo.mCrypto.mInitData = initData;
+    mInfo.mCrypto.mType = NS_LITERAL_STRING("cenc");
   }
 
   // Get the duration, and report it to the decoder if we have it.
   Microseconds duration;
   {
     MonitorAutoLock lock(mDemuxerMonitor);
     duration = mDemuxer->Duration();
   }
   if (duration != -1) {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mDecoder->SetMediaDuration(duration);
   }
 
   *aInfo = mInfo;
   *aTags = nullptr;
 
+  if (!IsWaitingMediaResources() && !IsWaitingOnCDMResource()) {
+    NS_ENSURE_TRUE(EnsureDecodersSetup(), NS_ERROR_FAILURE);
+  }
+
   MonitorAutoLock mon(mDemuxerMonitor);
   UpdateIndex();
 
   return NS_OK;
 }
 
+bool
+MP4Reader::EnsureDecodersSetup()
+{
+  if (mAreDecodersSetup) {
+    return !!mPlatform;
+  }
+
+  if (mIsEncrypted) {
+#ifdef MOZ_EME
+    // We have encrypted audio or video. We'll need a CDM to decrypt and
+    // possibly decode this. Wait until we've received a CDM from the
+    // JavaScript player app. Note: we still go through the motions here
+    // even if EME is disabled, so that if script tries and fails to create
+    // a CDM, we can detect that and notify chrome and show some UI explaining
+    // that we failed due to EME being disabled.
+    nsRefPtr<CDMProxy> proxy;
+    if (IsWaitingMediaResources()) {
+      return true;
+    }
+    MOZ_ASSERT(!IsWaitingMediaResources());
+
+    {
+      ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+      proxy = mDecoder->GetCDMProxy();
+    }
+    MOZ_ASSERT(proxy);
+
+    mPlatform = PlatformDecoderModule::CreateCDMWrapper(proxy,
+                                                        HasAudio(),
+                                                        HasVideo());
+    NS_ENSURE_TRUE(mPlatform, false);
+#else
+    // EME not supported.
+    return false;
+#endif
+  } else {
+    mPlatform = PlatformDecoderModule::Create();
+    NS_ENSURE_TRUE(mPlatform, false);
+  }
+
+  if (HasAudio()) {
+    NS_ENSURE_TRUE(IsSupportedAudioMimeType(mDemuxer->AudioConfig().mime_type),
+                   false);
+
+    mAudio.mDecoder = mPlatform->CreateAudioDecoder(mDemuxer->AudioConfig(),
+                                                    mAudio.mTaskQueue,
+                                                    mAudio.mCallback);
+    NS_ENSURE_TRUE(mAudio.mDecoder != nullptr, false);
+    nsresult rv = mAudio.mDecoder->Init();
+    NS_ENSURE_SUCCESS(rv, false);
+  }
+
+  if (HasVideo()) {
+    NS_ENSURE_TRUE(IsSupportedVideoMimeType(mDemuxer->VideoConfig().mime_type),
+                   false);
+
+    if (mSharedDecoderManager && mPlatform->SupportsSharedDecoders(mDemuxer->VideoConfig())) {
+      mVideo.mDecoder =
+        mSharedDecoderManager->CreateVideoDecoder(mPlatform,
+                                                  mDemuxer->VideoConfig(),
+                                                  mLayersBackendType,
+                                                  mDecoder->GetImageContainer(),
+                                                  mVideo.mTaskQueue,
+                                                  mVideo.mCallback);
+    } else {
+      mVideo.mDecoder = mPlatform->CreateVideoDecoder(mDemuxer->VideoConfig(),
+                                                      mLayersBackendType,
+                                                      mDecoder->GetImageContainer(),
+                                                      mVideo.mTaskQueue,
+                                                      mVideo.mCallback);
+    }
+    NS_ENSURE_TRUE(mVideo.mDecoder != nullptr, false);
+    nsresult rv = mVideo.mDecoder->Init();
+    NS_ENSURE_SUCCESS(rv, false);
+  }
+
+  mAreDecodersSetup = true;
+  return true;
+}
+
 void
 MP4Reader::ReadUpdatedMetadata(MediaInfo* aInfo)
 {
   *aInfo = mInfo;
 }
 
 bool
 MP4Reader::IsMediaSeekable()
@@ -574,20 +558,20 @@ MP4Reader::GetNextKeyframeTime()
   MonitorAutoLock mon(mDemuxerMonitor);
   return mVideo.mTrackDemuxer->GetNextKeyframeTime();
 }
 
 void
 MP4Reader::DisableHardwareAcceleration()
 {
   if (HasVideo() && mSharedDecoderManager) {
-    mPlatform->DisableHardwareAcceleration();
+    mSharedDecoderManager->DisableHardwareAcceleration();
 
     const VideoDecoderConfig& video = mDemuxer->VideoConfig();
-    if (!mSharedDecoderManager->Recreate(mPlatform, video, mLayersBackendType, mDecoder->GetImageContainer())) {
+    if (!mSharedDecoderManager->Recreate(video, mLayersBackendType, mDecoder->GetImageContainer())) {
       MonitorAutoLock mon(mVideo.mMonitor);
       mVideo.mError = true;
       if (mVideo.HasPromise()) {
         mVideo.RejectPromise(DECODE_ERROR, __func__);
       }
     } else {
       MonitorAutoLock lock(mVideo.mMonitor);
       ScheduleUpdate(kVideo);
@@ -613,16 +597,21 @@ MP4Reader::ShouldSkip(bool aSkipToNextKe
 
 nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MP4Reader::RequestVideoData(bool aSkipToNextKeyframe,
                             int64_t aTimeThreshold)
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   VLOG("skip=%d time=%lld", aSkipToNextKeyframe, aTimeThreshold);
 
+  if (!EnsureDecodersSetup()) {
+    NS_WARNING("Error constructing MP4 decoders");
+    return VideoDataPromise::CreateAndReject(DECODE_ERROR, __func__);
+  }
+
   if (mShutdown) {
     NS_WARNING("RequestVideoData on shutdown MP4Reader!");
     return VideoDataPromise::CreateAndReject(CANCELED, __func__);
   }
 
   MOZ_ASSERT(HasVideo() && mPlatform && mVideo.mDecoder);
 
   bool eos = false;
@@ -648,16 +637,22 @@ MP4Reader::RequestVideoData(bool aSkipTo
   return p;
 }
 
 nsRefPtr<MediaDecoderReader::AudioDataPromise>
 MP4Reader::RequestAudioData()
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   VLOG("");
+
+  if (!EnsureDecodersSetup()) {
+    NS_WARNING("Error constructing MP4 decoders");
+    return AudioDataPromise::CreateAndReject(DECODE_ERROR, __func__);
+  }
+
   if (mShutdown) {
     NS_WARNING("RequestAudioData on shutdown MP4Reader!");
     return AudioDataPromise::CreateAndReject(CANCELED, __func__);
   }
 
   MonitorAutoLock lock(mAudio.mMonitor);
   nsRefPtr<AudioDataPromise> p = mAudio.mPromise.Ensure(__func__);
   ScheduleUpdate(kAudio);
--- a/dom/media/fmp4/MP4Reader.h
+++ b/dom/media/fmp4/MP4Reader.h
@@ -98,16 +98,18 @@ public:
 
   virtual void DisableHardwareAcceleration() override;
 
 private:
 
   bool InitDemuxer();
   void ReturnOutput(MediaData* aData, TrackType aTrack);
 
+  bool EnsureDecodersSetup();
+
   // Sends input to decoder for aTrack, and output to the state machine,
   // if necessary.
   void Update(TrackType aTrack);
 
   // Enqueues a task to call Update(aTrack) on the decoder task queue.
   // Lock for corresponding track must be held.
   void ScheduleUpdate(TrackType aTrack);
 
@@ -130,17 +132,16 @@ private:
   void Error(mp4_demuxer::TrackType aTrack);
   void Flush(mp4_demuxer::TrackType aTrack);
   void DrainComplete(mp4_demuxer::TrackType aTrack);
   void UpdateIndex();
   bool IsSupportedAudioMimeType(const char* aMimeType);
   bool IsSupportedVideoMimeType(const char* aMimeType);
   void NotifyResourcesStatusChanged();
   void RequestCodecResource();
-  bool IsWaitingOnCodecResource();
   virtual bool IsWaitingOnCDMResource() override;
 
   Microseconds GetNextKeyframeTime();
   bool ShouldSkip(bool aSkipToNextKeyframe, int64_t aTimeThreshold);
 
   size_t SizeOfQueue(TrackType aTrack);
 
   nsRefPtr<MP4Stream> mStream;
@@ -280,16 +281,18 @@ private:
   bool mDemuxerInitialized;
 
   // True if we've gathered telemetry from an SPS.
   bool mFoundSPSForTelemetry;
 
   // Synchronized by decoder monitor.
   bool mIsEncrypted;
 
+  bool mAreDecodersSetup;
+
   bool mIndexReady;
   Monitor mDemuxerMonitor;
   nsRefPtr<SharedDecoderManager> mSharedDecoderManager;
 
 #if defined(MP4_READER_DORMANT_HEURISTIC)
   const bool mDormantEnabled;
 #endif
 };
--- a/dom/media/fmp4/PlatformDecoderModule.h
+++ b/dom/media/fmp4/PlatformDecoderModule.h
@@ -81,23 +81,16 @@ public:
   // that we use on on aTaskQueue to decode the decrypted stream.
   // This is called on the decode task queue.
   static already_AddRefed<PlatformDecoderModule>
   CreateCDMWrapper(CDMProxy* aProxy,
                    bool aHasAudio,
                    bool aHasVideo);
 #endif
 
-  // Called to shutdown the decoder module and cleanup state. The PDM
-  // is deleted immediately after Shutdown() is called. Shutdown() is
-  // called after Shutdown() has been called on all MediaDataDecoders
-  // created from this PlatformDecoderModule.
-  // This is called on the decode task queue.
-  virtual nsresult Shutdown() = 0;
-
   // Creates an H.264 decoder. The layers backend is passed in so that
   // decoders can determine whether hardware accelerated decoding can be used.
   // Asynchronous decoding of video should be done in runnables dispatched
   // to aVideoTaskQueue. If the task queue isn't needed, the decoder should
   // not hold a reference to it.
   // Output and errors should be returned to the reader via aCallback.
   // On Windows the task queue's threads in have MSCOM initialized with
   // COINIT_MULTITHREADED.
--- a/dom/media/fmp4/SharedDecoderManager.cpp
+++ b/dom/media/fmp4/SharedDecoderManager.cpp
@@ -7,17 +7,20 @@
 #include "SharedDecoderManager.h"
 #include "mp4_demuxer/DecoderData.h"
 
 namespace mozilla {
 
 class SharedDecoderCallback : public MediaDataDecoderCallback
 {
 public:
-  explicit SharedDecoderCallback(SharedDecoderManager* aManager) : mManager(aManager) {}
+  explicit SharedDecoderCallback(SharedDecoderManager* aManager)
+    : mManager(aManager)
+  {
+  }
 
   virtual void Output(MediaData* aData) override
   {
     if (mManager->mActiveCallback) {
       mManager->mActiveCallback->Output(aData);
     }
   }
   virtual void Error() override
@@ -61,52 +64,71 @@ SharedDecoderManager::SharedDecoderManag
   , mWaitForInternalDrain(false)
   , mMonitor("SharedDecoderProxy")
   , mDecoderReleasedResources(false)
 {
   MOZ_ASSERT(NS_IsMainThread()); // taskqueue must be created on main thread.
   mCallback = new SharedDecoderCallback(this);
 }
 
-SharedDecoderManager::~SharedDecoderManager() {}
+SharedDecoderManager::~SharedDecoderManager()
+{
+}
 
 already_AddRefed<MediaDataDecoder>
 SharedDecoderManager::CreateVideoDecoder(
   PlatformDecoderModule* aPDM,
   const mp4_demuxer::VideoDecoderConfig& aConfig,
-  layers::LayersBackend aLayersBackend, layers::ImageContainer* aImageContainer,
-  FlushableMediaTaskQueue* aVideoTaskQueue, MediaDataDecoderCallback* aCallback)
+  layers::LayersBackend aLayersBackend,
+  layers::ImageContainer* aImageContainer,
+  FlushableMediaTaskQueue* aVideoTaskQueue,
+  MediaDataDecoderCallback* aCallback)
 {
   if (!mDecoder) {
     // We use the manager's task queue for the decoder, rather than the one
     // passed in, so that none of the objects sharing the decoder can shutdown
     // the task queue while we're potentially still using it for a *different*
     // object also sharing the decoder.
-    mDecoder = aPDM->CreateVideoDecoder(
-      aConfig, aLayersBackend, aImageContainer, mTaskQueue, mCallback);
+    mDecoder = aPDM->CreateVideoDecoder(aConfig,
+                                        aLayersBackend,
+                                        aImageContainer,
+                                        mTaskQueue,
+                                        mCallback);
     if (!mDecoder) {
+      mPDM = nullptr;
       return nullptr;
     }
+    mPDM = aPDM;
     nsresult rv = mDecoder->Init();
     NS_ENSURE_SUCCESS(rv, nullptr);
   }
 
   nsRefPtr<SharedDecoderProxy> proxy(new SharedDecoderProxy(this, aCallback));
   return proxy.forget();
 }
 
+void
+SharedDecoderManager::DisableHardwareAcceleration()
+{
+  MOZ_ASSERT(mPDM);
+  mPDM->DisableHardwareAcceleration();
+}
+
 bool
-SharedDecoderManager::Recreate(PlatformDecoderModule* aPDM,
-                               const mp4_demuxer::VideoDecoderConfig& aConfig,
+SharedDecoderManager::Recreate(const mp4_demuxer::VideoDecoderConfig& aConfig,
                                layers::LayersBackend aLayersBackend,
                                layers::ImageContainer* aImageContainer)
 {
   mDecoder->Flush();
   mDecoder->Shutdown();
-  mDecoder = aPDM->CreateVideoDecoder(aConfig, aLayersBackend, aImageContainer, mTaskQueue, mCallback);
+  mDecoder = mPDM->CreateVideoDecoder(aConfig,
+                                      aLayersBackend,
+                                      aImageContainer,
+                                      mTaskQueue,
+                                      mCallback);
   if (!mDecoder) {
     return false;
   }
   nsresult rv = mDecoder->Init();
   return rv == NS_OK;
 }
 
 void
@@ -163,30 +185,35 @@ SharedDecoderManager::ReleaseMediaResour
 
 void
 SharedDecoderManager::Shutdown()
 {
   if (mDecoder) {
     mDecoder->Shutdown();
     mDecoder = nullptr;
   }
+  mPDM = nullptr;
   if (mTaskQueue) {
     mTaskQueue->BeginShutdown();
     mTaskQueue->AwaitShutdownAndIdle();
     mTaskQueue = nullptr;
   }
 }
 
-SharedDecoderProxy::SharedDecoderProxy(
-  SharedDecoderManager* aManager, MediaDataDecoderCallback* aCallback)
-  : mManager(aManager), mCallback(aCallback)
+SharedDecoderProxy::SharedDecoderProxy(SharedDecoderManager* aManager,
+                                       MediaDataDecoderCallback* aCallback)
+  : mManager(aManager)
+  , mCallback(aCallback)
 {
 }
 
-SharedDecoderProxy::~SharedDecoderProxy() { Shutdown(); }
+SharedDecoderProxy::~SharedDecoderProxy()
+{
+  Shutdown();
+}
 
 nsresult
 SharedDecoderProxy::Init()
 {
   return NS_OK;
 }
 
 nsresult
--- a/dom/media/fmp4/SharedDecoderManager.h
+++ b/dom/media/fmp4/SharedDecoderManager.h
@@ -23,37 +23,39 @@ public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedDecoderManager)
 
   SharedDecoderManager();
 
   already_AddRefed<MediaDataDecoder> CreateVideoDecoder(
     PlatformDecoderModule* aPDM,
     const mp4_demuxer::VideoDecoderConfig& aConfig,
     layers::LayersBackend aLayersBackend,
-    layers::ImageContainer* aImageContainer, FlushableMediaTaskQueue* aVideoTaskQueue,
+    layers::ImageContainer* aImageContainer,
+    FlushableMediaTaskQueue* aVideoTaskQueue,
     MediaDataDecoderCallback* aCallback);
 
   void SetReader(MediaDecoderReader* aReader);
   void Select(SharedDecoderProxy* aProxy);
   void SetIdle(MediaDataDecoder* aProxy);
   void ReleaseMediaResources();
   void Shutdown();
 
   friend class SharedDecoderProxy;
   friend class SharedDecoderCallback;
 
-  bool Recreate(PlatformDecoderModule* aPDM,
-    const mp4_demuxer::VideoDecoderConfig& aConfig,
-    layers::LayersBackend aLayersBackend,
-    layers::ImageContainer* aImageContainer);
+  void DisableHardwareAcceleration();
+  bool Recreate(const mp4_demuxer::VideoDecoderConfig& aConfig,
+                layers::LayersBackend aLayersBackend,
+                layers::ImageContainer* aImageContainer);
 
 private:
   virtual ~SharedDecoderManager();
   void DrainComplete();
 
+  nsRefPtr<PlatformDecoderModule> mPDM;
   nsRefPtr<MediaDataDecoder> mDecoder;
   nsRefPtr<FlushableMediaTaskQueue> mTaskQueue;
   SharedDecoderProxy* mActiveProxy;
   MediaDataDecoderCallback* mActiveCallback;
   nsAutoPtr<MediaDataDecoderCallback> mCallback;
   bool mWaitForInternalDrain;
   Monitor mMonitor;
   bool mDecoderReleasedResources;
--- a/dom/media/fmp4/android/AndroidDecoderModule.cpp
+++ b/dom/media/fmp4/android/AndroidDecoderModule.cpp
@@ -294,22 +294,16 @@ AndroidDecoderModule::CreateAudioDecoder
 
   nsRefPtr<MediaDataDecoder> decoder =
     new AudioDataDecoder(aConfig, format, aCallback);
 
   return decoder.forget();
 
 }
 
-
-nsresult AndroidDecoderModule::Shutdown()
-{
-  return NS_OK;
-}
-
 MediaCodecDataDecoder::MediaCodecDataDecoder(MediaData::Type aType,
                                              const char* aMimeType,
                                              MediaFormat::Param aFormat,
                                              MediaDataDecoderCallback* aCallback)
   : mType(aType)
   , mMimeType(strdup(aMimeType))
   , mFormat(aFormat)
   , mCallback(aCallback)
--- a/dom/media/fmp4/android/AndroidDecoderModule.h
+++ b/dom/media/fmp4/android/AndroidDecoderModule.h
@@ -14,18 +14,16 @@
 #include <queue>
 
 namespace mozilla {
 
 typedef std::queue<mp4_demuxer::MP4Sample*> SampleQueue;
 
 class AndroidDecoderModule : public PlatformDecoderModule {
 public:
-  virtual nsresult Shutdown() override;
-
   virtual already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
                      FlushableMediaTaskQueue* aVideoTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
   virtual already_AddRefed<MediaDataDecoder>
--- a/dom/media/fmp4/apple/AppleDecoderModule.cpp
+++ b/dom/media/fmp4/apple/AppleDecoderModule.cpp
@@ -31,22 +31,56 @@ namespace mozilla {
 #define VDA_RESOLUTION_THRESHOLD 720
 
 bool AppleDecoderModule::sInitialized = false;
 bool AppleDecoderModule::sIsVTAvailable = false;
 bool AppleDecoderModule::sIsVTHWAvailable = false;
 bool AppleDecoderModule::sIsVDAAvailable = false;
 bool AppleDecoderModule::sForceVDA = false;
 
+class LinkTask : public nsRunnable {
+public:
+  NS_IMETHOD Run() override {
+    MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+    MOZ_ASSERT(AppleDecoderModule::sInitialized);
+    if (AppleDecoderModule::sIsVDAAvailable) {
+      AppleVDALinker::Link();
+    }
+    if (AppleDecoderModule::sIsVTAvailable) {
+      AppleVTLinker::Link();
+      AppleCMLinker::Link();
+    }
+    return NS_OK;
+  }
+};
+
+class UnlinkTask : public nsRunnable {
+public:
+  NS_IMETHOD Run() override {
+    MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+    MOZ_ASSERT(AppleDecoderModule::sInitialized);
+    if (AppleDecoderModule::sIsVDAAvailable) {
+      AppleVDALinker::Unlink();
+    }
+    if (AppleDecoderModule::sIsVTAvailable) {
+      AppleVTLinker::Unlink();
+      AppleCMLinker::Unlink();
+    }
+    return NS_OK;
+  }
+};
+
 AppleDecoderModule::AppleDecoderModule()
 {
 }
 
 AppleDecoderModule::~AppleDecoderModule()
 {
+  nsCOMPtr<nsIRunnable> task(new UnlinkTask());
+  NS_DispatchToMainThread(task);
 }
 
 /* static */
 void
 AppleDecoderModule::Init()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
 
@@ -99,69 +133,29 @@ AppleDecoderModule::CanDecode()
       nsCOMPtr<nsIRunnable> task(new InitTask());
       NS_DispatchToMainThread(task, NS_DISPATCH_SYNC);
     }
   }
 
   return (sIsVDAAvailable || sIsVTAvailable) ? NS_OK : NS_ERROR_NO_INTERFACE;
 }
 
-class LinkTask : public nsRunnable {
-public:
-  NS_IMETHOD Run() override {
-    MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
-    MOZ_ASSERT(AppleDecoderModule::sInitialized);
-    if (AppleDecoderModule::sIsVDAAvailable) {
-      AppleVDALinker::Link();
-    }
-    if (AppleDecoderModule::sIsVTAvailable) {
-      AppleVTLinker::Link();
-      AppleCMLinker::Link();
-    }
-    return NS_OK;
-  }
-};
-
 nsresult
 AppleDecoderModule::Startup()
 {
   if (!sIsVDAAvailable && !sIsVTAvailable) {
     return NS_ERROR_FAILURE;
   }
 
   nsCOMPtr<nsIRunnable> task(new LinkTask());
   NS_DispatchToMainThread(task, NS_DISPATCH_SYNC);
 
   return NS_OK;
 }
 
-class UnlinkTask : public nsRunnable {
-public:
-  NS_IMETHOD Run() override {
-    MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
-    MOZ_ASSERT(AppleDecoderModule::sInitialized);
-    if (AppleDecoderModule::sIsVDAAvailable) {
-      AppleVDALinker::Unlink();
-    }
-    if (AppleDecoderModule::sIsVTAvailable) {
-      AppleVTLinker::Unlink();
-      AppleCMLinker::Unlink();
-    }
-    return NS_OK;
-  }
-};
-
-nsresult
-AppleDecoderModule::Shutdown()
-{
-  nsCOMPtr<nsIRunnable> task(new UnlinkTask());
-  NS_DispatchToMainThread(task);
-  return NS_OK;
-}
-
 already_AddRefed<MediaDataDecoder>
 AppleDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                                        layers::LayersBackend aLayersBackend,
                                        layers::ImageContainer* aImageContainer,
                                        FlushableMediaTaskQueue* aVideoTaskQueue,
                                        MediaDataDecoderCallback* aCallback)
 {
   nsRefPtr<MediaDataDecoder> decoder;
--- a/dom/media/fmp4/apple/AppleDecoderModule.h
+++ b/dom/media/fmp4/apple/AppleDecoderModule.h
@@ -13,20 +13,16 @@ namespace mozilla {
 
 class AppleDecoderModule : public PlatformDecoderModule {
 public:
   AppleDecoderModule();
   virtual ~AppleDecoderModule();
 
   virtual nsresult Startup() override;
 
-  // Called when the decoders have shutdown. Main thread only.
-  // Does this really need to be main thread only????
-  virtual nsresult Shutdown() override;
-
   // Decode thread.
   virtual already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
                      FlushableMediaTaskQueue* aVideoTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
--- a/dom/media/fmp4/apple/AppleVDALinker.cpp
+++ b/dom/media/fmp4/apple/AppleVDALinker.cpp
@@ -89,17 +89,21 @@ AppleVDALinker::Unlink()
   // We'll be called by multiple Decoders, one intantiated for
   // each media element. Therefore we receive must maintain a
   // reference count to avoidunloading our symbols when other
   // instances still need them.
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sRefCount > 0, "Unbalanced Unlink()");
   --sRefCount;
   if (sLink && sRefCount < 1) {
-    LOG("Unlinking VideoToolbox framework.");
+    LOG("Unlinking VideoDecodeAcceleration framework.");
+#define LINK_FUNC(func)                                                   \
+    func = nullptr;
+#include "AppleVDAFunctions.h"
+#undef LINK_FUNC
     dlclose(sLink);
     sLink = nullptr;
     skPropWidth = nullptr;
     skPropHeight = nullptr;
     skPropSourceFormat = nullptr;
     skPropAVCCData = nullptr;
     sLinkStatus = LinkStatus_INIT;
   }
--- a/dom/media/fmp4/apple/AppleVTLinker.cpp
+++ b/dom/media/fmp4/apple/AppleVTLinker.cpp
@@ -93,16 +93,20 @@ AppleVTLinker::Unlink()
   // each media element. Therefore we receive must maintain a
   // reference count to avoidunloading our symbols when other
   // instances still need them.
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sRefCount > 0, "Unbalanced Unlink()");
   --sRefCount;
   if (sLink && sRefCount < 1) {
     LOG("Unlinking VideoToolbox framework.");
+#define LINK_FUNC(func)                                                   \
+    func = nullptr;
+#include "AppleVTFunctions.h"
+#undef LINK_FUNC
     dlclose(sLink);
     sLink = nullptr;
     skPropEnableHWAccel = nullptr;
     skPropUsingHWAccel = nullptr;
     sLinkStatus = LinkStatus_INIT;
   }
 }
 
--- a/dom/media/fmp4/eme/EMEDecoderModule.cpp
+++ b/dom/media/fmp4/eme/EMEDecoderModule.cpp
@@ -203,25 +203,16 @@ EMEDecoderModule::EMEDecoderModule(CDMPr
   , mCDMDecodesVideo(aCDMDecodesVideo)
 {
 }
 
 EMEDecoderModule::~EMEDecoderModule()
 {
 }
 
-nsresult
-EMEDecoderModule::Shutdown()
-{
-  if (mPDM) {
-    return mPDM->Shutdown();
-  }
-  return NS_OK;
-}
-
 static already_AddRefed<MediaDataDecoderProxy>
 CreateDecoderWrapper(MediaDataDecoderCallback* aCallback, CDMProxy* aProxy, FlushableMediaTaskQueue* aTaskQueue)
 {
   nsCOMPtr<mozIGeckoMediaPluginService> gmpService = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   if (!gmpService) {
     return nullptr;
   }
 
--- a/dom/media/fmp4/eme/EMEDecoderModule.h
+++ b/dom/media/fmp4/eme/EMEDecoderModule.h
@@ -23,19 +23,16 @@ private:
 public:
   EMEDecoderModule(CDMProxy* aProxy,
                    PlatformDecoderModule* aPDM,
                    bool aCDMDecodesAudio,
                    bool aCDMDecodesVideo);
 
   virtual ~EMEDecoderModule();
 
-  // Called when the decoders have shutdown. Main thread only.
-  virtual nsresult Shutdown() override;
-
   // Decode thread.
   virtual already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                     layers::LayersBackend aLayersBackend,
                     layers::ImageContainer* aImageContainer,
                     FlushableMediaTaskQueue* aVideoTaskQueue,
                     MediaDataDecoderCallback* aCallback) override;
 
--- a/dom/media/fmp4/ffmpeg/FFmpegDecoderModule.h
+++ b/dom/media/fmp4/ffmpeg/FFmpegDecoderModule.h
@@ -23,18 +23,16 @@ public:
   {
     nsRefPtr<PlatformDecoderModule> pdm = new FFmpegDecoderModule();
     return pdm.forget();
   }
 
   FFmpegDecoderModule() {}
   virtual ~FFmpegDecoderModule() {}
 
-  virtual nsresult Shutdown() override { return NS_OK; }
-
   virtual already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
                      FlushableMediaTaskQueue* aVideoTaskQueue,
                      MediaDataDecoderCallback* aCallback) override
   {
     nsRefPtr<MediaDataDecoder> decoder =
--- a/dom/media/fmp4/gmp/GMPDecoderModule.cpp
+++ b/dom/media/fmp4/gmp/GMPDecoderModule.cpp
@@ -16,22 +16,16 @@ namespace mozilla {
 GMPDecoderModule::GMPDecoderModule()
 {
 }
 
 GMPDecoderModule::~GMPDecoderModule()
 {
 }
 
-nsresult
-GMPDecoderModule::Shutdown()
-{
-  return NS_OK;
-}
-
 static already_AddRefed<MediaDataDecoderProxy>
 CreateDecoderWrapper(MediaDataDecoderCallback* aCallback)
 {
   nsCOMPtr<mozIGeckoMediaPluginService> gmpService = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   if (!gmpService) {
     return nullptr;
   }
 
--- a/dom/media/fmp4/gmp/GMPDecoderModule.h
+++ b/dom/media/fmp4/gmp/GMPDecoderModule.h
@@ -12,19 +12,16 @@
 namespace mozilla {
 
 class GMPDecoderModule : public PlatformDecoderModule {
 public:
   GMPDecoderModule();
 
   virtual ~GMPDecoderModule();
 
-  // Called when the decoders have shutdown. Main thread only.
-  virtual nsresult Shutdown() override;
-
   // Decode thread.
   virtual already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
                      FlushableMediaTaskQueue* aVideoTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
--- a/dom/media/fmp4/gonk/GonkDecoderModule.cpp
+++ b/dom/media/fmp4/gonk/GonkDecoderModule.cpp
@@ -21,22 +21,16 @@ GonkDecoderModule::~GonkDecoderModule()
 
 /* static */
 void
 GonkDecoderModule::Init()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
 }
 
-nsresult
-GonkDecoderModule::Shutdown()
-{
-  return NS_OK;
-}
-
 already_AddRefed<MediaDataDecoder>
 GonkDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                                      mozilla::layers::LayersBackend aLayersBackend,
                                      mozilla::layers::ImageContainer* aImageContainer,
                                      FlushableMediaTaskQueue* aVideoTaskQueue,
                                      MediaDataDecoderCallback* aCallback)
 {
   nsRefPtr<MediaDataDecoder> decoder =
--- a/dom/media/fmp4/gonk/GonkDecoderModule.h
+++ b/dom/media/fmp4/gonk/GonkDecoderModule.h
@@ -11,19 +11,16 @@
 
 namespace mozilla {
 
 class GonkDecoderModule : public PlatformDecoderModule {
 public:
   GonkDecoderModule();
   virtual ~GonkDecoderModule();
 
-  // Called when the decoders have shutdown.
-  virtual nsresult Shutdown() override;
-
   // Decode thread.
   virtual already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                      mozilla::layers::LayersBackend aLayersBackend,
                      mozilla::layers::ImageContainer* aImageContainer,
                      FlushableMediaTaskQueue* aVideoTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
--- a/dom/media/fmp4/wmf/WMFDecoderModule.cpp
+++ b/dom/media/fmp4/wmf/WMFDecoderModule.cpp
@@ -23,16 +23,18 @@ bool WMFDecoderModule::sIsWMFEnabled = f
 bool WMFDecoderModule::sDXVAEnabled = false;
 
 WMFDecoderModule::WMFDecoderModule()
 {
 }
 
 WMFDecoderModule::~WMFDecoderModule()
 {
+  DebugOnly<HRESULT> hr = wmf::MFShutdown();
+  NS_ASSERTION(SUCCEEDED(hr), "MFShutdown failed");
 }
 
 /* static */
 void
 WMFDecoderModule::Init()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
   sIsWMFEnabled = Preferences::GetBool("media.windows-media-foundation.enabled", false);
@@ -53,24 +55,16 @@ WMFDecoderModule::Startup()
   }
   if (FAILED(wmf::MFStartup())) {
     NS_WARNING("Failed to initialize Windows Media Foundation");
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
-nsresult
-WMFDecoderModule::Shutdown()
-{
-  DebugOnly<HRESULT> hr = wmf::MFShutdown();
-  NS_ASSERTION(SUCCEEDED(hr), "MFShutdown failed");
-  return NS_OK;
-}
-
 already_AddRefed<MediaDataDecoder>
 WMFDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                                      layers::LayersBackend aLayersBackend,
                                      layers::ImageContainer* aImageContainer,
                                      FlushableMediaTaskQueue* aVideoTaskQueue,
                                      MediaDataDecoderCallback* aCallback)
 {
   nsRefPtr<MediaDataDecoder> decoder =
--- a/dom/media/fmp4/wmf/WMFDecoderModule.h
+++ b/dom/media/fmp4/wmf/WMFDecoderModule.h
@@ -14,19 +14,16 @@ namespace mozilla {
 class WMFDecoderModule : public PlatformDecoderModule {
 public:
   WMFDecoderModule();
   virtual ~WMFDecoderModule();
 
   // Initializes the module, loads required dynamic libraries, etc.
   virtual nsresult Startup() override;
 
-  // Called when the decoders have shutdown.
-  virtual nsresult Shutdown() override;
-
   virtual already_AddRefed<MediaDataDecoder>
   CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                      layers::LayersBackend aLayersBackend,
                      layers::ImageContainer* aImageContainer,
                      FlushableMediaTaskQueue* aVideoTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
   virtual already_AddRefed<MediaDataDecoder>
--- a/dom/media/gmp/GMPService.cpp
+++ b/dom/media/gmp/GMPService.cpp
@@ -660,36 +660,49 @@ GeckoMediaPluginService::LoadFromEnviron
   }
 
   mScannedPluginOnDisk = true;
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginService::PathRunnable::Run()
 {
-  if (mAdd) {
+  if (mOperation == ADD) {
     mService->AddOnGMPThread(mPath);
   } else {
-    mService->RemoveOnGMPThread(mPath);
+    mService->RemoveOnGMPThread(mPath,
+                                mOperation == REMOVE_AND_DELETE_FROM_DISK);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginService::AddPluginDirectory(const nsAString& aDirectory)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  return GMPDispatch(new PathRunnable(this, aDirectory, true));
+  return GMPDispatch(new PathRunnable(this, aDirectory,
+                                      PathRunnable::EOperation::ADD));
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginService::RemovePluginDirectory(const nsAString& aDirectory)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  return GMPDispatch(new PathRunnable(this, aDirectory, false));
+  return GMPDispatch(new PathRunnable(this, aDirectory,
+                                      PathRunnable::EOperation::REMOVE));
+}
+
+NS_IMETHODIMP
+GeckoMediaPluginService::RemoveAndDeletePluginDirectory(
+  const nsAString& aDirectory)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return GMPDispatch(
+    new PathRunnable(this, aDirectory,
+                     PathRunnable::EOperation::REMOVE_AND_DELETE_FROM_DISK));
 }
 
 class DummyRunnable : public nsRunnable {
 public:
   NS_IMETHOD Run() { return NS_OK; }
 };
 
 NS_IMETHODIMP
@@ -910,33 +923,38 @@ GeckoMediaPluginService::AddOnGMPThread(
     return;
   }
 
   MutexAutoLock lock(mMutex);
   mPlugins.AppendElement(gmp);
 }
 
 void
-GeckoMediaPluginService::RemoveOnGMPThread(const nsAString& aDirectory)
+GeckoMediaPluginService::RemoveOnGMPThread(const nsAString& aDirectory,
+                                           const bool aDeleteFromDisk)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, NS_LossyConvertUTF16toASCII(aDirectory).get()));
 
   nsCOMPtr<nsIFile> directory;
   nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   MutexAutoLock lock(mMutex);
   for (size_t i = 0; i < mPlugins.Length(); ++i) {
     nsCOMPtr<nsIFile> pluginpath = mPlugins[i]->GetDirectory();
     bool equals;
     if (NS_SUCCEEDED(directory->Equals(pluginpath, &equals)) && equals) {
+      mPlugins[i]->AbortAsyncShutdown();
       mPlugins[i]->CloseActive(true);
+      if (aDeleteFromDisk) {
+        pluginpath->Remove(true);
+      }
       mPlugins.RemoveElementAt(i);
       return;
     }
   }
   NS_WARNING("Removing GMP which was never added.");
   nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
   cs->LogStringMessage(MOZ_UTF16("Removing GMP which was never added."));
 }
--- a/dom/media/gmp/GMPService.h
+++ b/dom/media/gmp/GMPService.h
@@ -66,18 +66,19 @@ private:
 
   void UnloadPlugins();
   void CrashPlugins();
   void SetAsyncShutdownComplete();
 
   void LoadFromEnvironment();
   void ProcessPossiblePlugin(nsIFile* aDir);
 
-  void AddOnGMPThread(const nsAString& aSearchDir);
-  void RemoveOnGMPThread(const nsAString& aSearchDir);
+  void AddOnGMPThread(const nsAString& aDirectory);
+  void RemoveOnGMPThread(const nsAString& aDirectory,
+                         const bool aDeleteFromDisk);
 
   nsresult SetAsyncShutdownTimeout();
 
   struct DirectoryFilter {
     virtual bool operator()(nsIFile* aPath) = 0;
     ~DirectoryFilter() {}
   };
   void ClearNodeIdAndPlugin(DirectoryFilter& aFilter);
@@ -91,29 +92,35 @@ protected:
 private:
   GMPParent* ClonePlugin(const GMPParent* aOriginal);
   nsresult EnsurePluginsOnDiskScanned();
   nsresult InitStorage();
 
   class PathRunnable : public nsRunnable
   {
   public:
-    PathRunnable(GeckoMediaPluginService* service, const nsAString& path,
-                 bool add)
-      : mService(service)
-      , mPath(path)
-      , mAdd(add)
+    enum EOperation {
+      ADD,
+      REMOVE,
+      REMOVE_AND_DELETE_FROM_DISK,
+    };
+
+    PathRunnable(GeckoMediaPluginService* aService, const nsAString& aPath,
+                 EOperation aOperation)
+      : mService(aService)
+      , mPath(aPath)
+      , mOperation(aOperation)
     { }
 
     NS_DECL_NSIRUNNABLE
 
   private:
     nsRefPtr<GeckoMediaPluginService> mService;
     nsString mPath;
-    bool mAdd;
+    EOperation mOperation;
   };
 
   Mutex mMutex; // Protects mGMPThread and mShuttingDown and mPlugins
   nsTArray<nsRefPtr<GMPParent>> mPlugins;
   nsCOMPtr<nsIThread> mGMPThread;
   bool mShuttingDown;
   bool mShuttingDownOnGMPThread;
 
--- a/dom/media/gmp/mozIGeckoMediaPluginService.idl
+++ b/dom/media/gmp/mozIGeckoMediaPluginService.idl
@@ -21,17 +21,17 @@ class GMPVideoHost;
 [ptr] native GMPVideoDecoderProxy(GMPVideoDecoderProxy);
 [ptr] native GMPVideoEncoderProxy(GMPVideoEncoderProxy);
 [ptr] native GMPVideoHost(GMPVideoHost);
 [ptr] native MessageLoop(MessageLoop);
 [ptr] native TagArray(nsTArray<nsCString>);
 [ptr] native GMPDecryptorProxy(GMPDecryptorProxy);
 [ptr] native GMPAudioDecoderProxy(GMPAudioDecoderProxy);
 
-[scriptable, uuid(56cc105f-dd27-4752-83e0-371908edba04)]
+[scriptable, uuid(f71e6e57-5175-4cf3-8cc2-629273a75b67)]
 interface mozIGeckoMediaPluginService : nsISupports
 {
 
   /**
    * The GMP thread. Callable from any thread.
    */
   readonly attribute nsIThread thread;
 
@@ -93,16 +93,22 @@ interface mozIGeckoMediaPluginService : 
 
   /**
    * Remove a directory for gecko media plugins.
    * @note Main-thread API.
    */
   void removePluginDirectory(in AString directory);
 
   /**
+   * Remove a directory for gecko media plugins and delete it from disk.
+   * @note Main-thread API.
+   */
+  void removeAndDeletePluginDirectory(in AString directory);
+
+  /**
    * Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple.
    */
   ACString getNodeId(in AString origin,
                      in AString topLevelOrigin,
                      in bool inPrivateBrowsingMode);
 
   /**
    * Clears storage data associated with the origin.
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -88,16 +88,35 @@ MediaSourceReader::IsWaitingMediaResourc
     if (!mEssentialTrackBuffers[i]->IsReady()) {
       return true;
     }
   }
 
   return !mHasEssentialTrackBuffers;
 }
 
+bool
+MediaSourceReader::IsWaitingOnCDMResource()
+{
+#ifdef MOZ_EME
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  MOZ_ASSERT(!IsWaitingMediaResources());
+
+  for (auto& trackBuffer : mTrackBuffers) {
+    if (trackBuffer->IsWaitingOnCDMResource()) {
+      return true;
+    }
+  }
+
+  return mInfo.IsEncrypted() && !mCDMProxy;
+#else
+  return false;
+#endif
+}
+
 size_t
 MediaSourceReader::SizeOfVideoQueueInFrames()
 {
   if (!GetVideoReader()) {
     MSE_DEBUG("called with no video reader");
     return 0;
   }
   return GetVideoReader()->SizeOfVideoQueueInFrames();
@@ -1015,16 +1034,32 @@ MediaSourceReader::MaybeNotifyHaveData()
   if (!IsSeeking() && mVideoTrack && HaveData(mLastVideoTime, MediaData::VIDEO_DATA)) {
     haveVideo = true;
     WaitPromise(MediaData::VIDEO_DATA).ResolveIfExists(MediaData::VIDEO_DATA, __func__);
   }
   MSE_DEBUG("isSeeking=%d haveAudio=%d, haveVideo=%d",
             IsSeeking(), haveAudio, haveVideo);
 }
 
+static void
+CombineEncryptionData(EncryptionInfo& aTo, const EncryptionInfo& aFrom)
+{
+  if (!aFrom.mIsEncrypted) {
+    return;
+  }
+  aTo.mIsEncrypted = true;
+
+  if (!aTo.mType.IsEmpty() && !aTo.mType.Equals(aFrom.mType)) {
+    NS_WARNING("mismatched encryption types");
+  }
+
+  aTo.mType = aFrom.mType;
+  aTo.mInitData.AppendElements(aFrom.mInitData);
+}
+
 nsresult
 MediaSourceReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   MSE_DEBUG("tracks=%u/%u audio=%p video=%p",
             mEssentialTrackBuffers.Length(), mTrackBuffers.Length(),
             mAudioTrack.get(), mVideoTrack.get());
@@ -1038,30 +1073,30 @@ MediaSourceReader::ReadMetadata(MediaInf
 
   if (mAudioTrack) {
     MOZ_ASSERT(mAudioTrack->IsReady());
     mAudioSourceDecoder = mAudioTrack->Decoders()[0];
 
     const MediaInfo& info = GetAudioReader()->GetMediaInfo();
     MOZ_ASSERT(info.HasAudio());
     mInfo.mAudio = info.mAudio;
-    mInfo.mIsEncrypted = mInfo.mIsEncrypted || info.mIsEncrypted;
+    CombineEncryptionData(mInfo.mCrypto, info.mCrypto);
     MSE_DEBUG("audio reader=%p duration=%lld",
               mAudioSourceDecoder.get(),
               mAudioSourceDecoder->GetReader()->GetDecoder()->GetMediaDuration());
   }
 
   if (mVideoTrack) {
     MOZ_ASSERT(mVideoTrack->IsReady());
     mVideoSourceDecoder = mVideoTrack->Decoders()[0];
 
     const MediaInfo& info = GetVideoReader()->GetMediaInfo();
     MOZ_ASSERT(info.HasVideo());
     mInfo.mVideo = info.mVideo;
-    mInfo.mIsEncrypted = mInfo.mIsEncrypted || info.mIsEncrypted;
+    CombineEncryptionData(mInfo.mCrypto, info.mCrypto);
     MSE_DEBUG("video reader=%p duration=%lld",
               GetVideoReader(),
               GetVideoReader()->GetDecoder()->GetMediaDuration());
   }
 
   *aInfo = mInfo;
   *aTags = nullptr; // TODO: Handle metadata.
 
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -40,16 +40,17 @@ public:
     return NS_OK;
   }
 
   // Indicates the point in time at which the reader should consider
   // registered TrackBuffers essential for initialization.
   void PrepareInitialization();
 
   bool IsWaitingMediaResources() override;
+  bool IsWaitingOnCDMResource() override;
 
   nsRefPtr<AudioDataPromise> RequestAudioData() override;
   nsRefPtr<VideoDataPromise>
   RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) override;
 
   virtual size_t SizeOfVideoQueueInFrames() override;
   virtual size_t SizeOfAudioQueueInFrames() override;
 
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -42,16 +42,17 @@ extern PRLogModuleInfo* GetMediaSourceLo
 namespace mozilla {
 
 TrackBuffer::TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType)
   : mParentDecoder(aParentDecoder)
   , mType(aType)
   , mLastStartTimestamp(0)
   , mLastTimestampOffset(0)
   , mAdjustedTimestamp(0)
+  , mIsWaitingOnCDM(false)
   , mShutdown(false)
 {
   MOZ_COUNT_CTOR(TrackBuffer);
   mParser = ContainerParser::CreateForMIMEType(aType);
   mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool());
   aParentDecoder->AddTrackBuffer(this);
   mDecoderPerSegment = Preferences::GetBool("media.mediasource.decoder-per-segment", false);
   MSE_DEBUG("TrackBuffer created for parent decoder %p", aParentDecoder);
@@ -583,16 +584,17 @@ TrackBuffer::InitializeDecoder(SourceBuf
   // to leave the monitor while we call it. For the rest of this function
   // we want to hold the monitor though, since we run on a different task queue
   // from the reader and interact heavily with it.
   mParentDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
 
   if (mCurrentDecoder != aDecoder) {
     MSE_DEBUG("append was cancelled. Aborting initialization.");
+    RemoveDecoder(aDecoder);
     // If we reached this point, the SourceBuffer would have disconnected
     // the promise. So no need to reject it.
     return;
   }
 
   // We may be shut down at any time by the reader on another thread. So we need
   // to check for this each time we acquire the monitor. If that happens, we
   // need to abort immediately, because the reader has forgotten about us, and
@@ -636,22 +638,22 @@ TrackBuffer::InitializeDecoder(SourceBuf
 
   reader->SetIdle();
   if (mShutdown) {
     MSE_DEBUG("was shut down while reading metadata. Aborting initialization.");
     return;
   }
   if (mCurrentDecoder != aDecoder) {
     MSE_DEBUG("append was cancelled. Aborting initialization.");
+    RemoveDecoder(aDecoder);
     return;
   }
 
   if (NS_SUCCEEDED(rv) && reader->IsWaitingOnCDMResource()) {
-    mWaitingDecoders.AppendElement(aDecoder);
-    return;
+    mIsWaitingOnCDM = true;
   }
 
   aDecoder->SetTaskQueue(nullptr);
 
   if (NS_FAILED(rv) || (!mi.HasVideo() && !mi.HasAudio())) {
     // XXX: Need to signal error back to owning SourceBuffer.
     MSE_DEBUG("Reader %p failed to initialize rv=%x audio=%d video=%d",
               reader, rv, mi.HasAudio(), mi.HasVideo());
@@ -688,16 +690,17 @@ TrackBuffer::CompleteInitializeDecoder(S
     MSE_DEBUG("was shutdown. Aborting initialization.");
     return;
   }
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   if (mCurrentDecoder != aDecoder) {
     MSE_DEBUG("append was cancelled. Aborting initialization.");
     // If we reached this point, the SourceBuffer would have disconnected
     // the promise. So no need to reject it.
+    RemoveDecoder(aDecoder);
     return;
   }
 
   if (mShutdown) {
     MSE_DEBUG("was shut down. Aborting initialization.");
     RemoveDecoder(aDecoder);
     return;
   }
@@ -803,16 +806,22 @@ bool
 TrackBuffer::IsReady()
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   MOZ_ASSERT((mInfo.HasAudio() || mInfo.HasVideo()) || mInitializedDecoders.IsEmpty());
   return mInfo.HasAudio() || mInfo.HasVideo();
 }
 
 bool
+TrackBuffer::IsWaitingOnCDMResource()
+{
+  return mIsWaitingOnCDM;
+}
+
+bool
 TrackBuffer::ContainsTime(int64_t aTime, int64_t aTolerance)
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   for (uint32_t i = 0; i < mInitializedDecoders.Length(); ++i) {
     nsRefPtr<dom::TimeRanges> r = new dom::TimeRanges();
     mInitializedDecoders[i]->GetBuffered(r);
     if (r->Find(double(aTime) / USECS_PER_S,
                 double(aTolerance) / USECS_PER_S) != dom::TimeRanges::NoIndex) {
@@ -870,31 +879,22 @@ TrackBuffer::Decoders()
 }
 
 #ifdef MOZ_EME
 nsresult
 TrackBuffer::SetCDMProxy(CDMProxy* aProxy)
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
 
-  for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
-    nsresult rv = mDecoders[i]->SetCDMProxy(aProxy);
-    NS_ENSURE_SUCCESS(rv, rv);
+  for (auto& decoder : mDecoders) {
+    decoder->SetCDMProxy(aProxy);
   }
 
-  for (uint32_t i = 0; i < mWaitingDecoders.Length(); ++i) {
-    CDMCaps::AutoLock caps(aProxy->Capabilites());
-    caps.CallOnMainThreadWhenCapsAvailable(
-      NS_NewRunnableMethodWithArg<SourceBufferDecoder*>(this,
-                                                        &TrackBuffer::QueueInitializeDecoder,
-                                                        mWaitingDecoders[i]));
-  }
-
-  mWaitingDecoders.Clear();
-
+  mIsWaitingOnCDM = false;
+  mParentDecoder->NotifyWaitingForResourcesStatusChanged();
   return NS_OK;
 }
 #endif
 
 #if defined(DEBUG)
 void
 TrackBuffer::Dump(const char* aPath)
 {
--- a/dom/media/mediasource/TrackBuffer.h
+++ b/dom/media/mediasource/TrackBuffer.h
@@ -71,16 +71,18 @@ public:
 
   // Returns true if an init segment has been appended.
   bool HasInitSegment();
 
   // Returns true iff mParser->HasInitData() and the decoder using that init
   // segment has successfully initialized by setting mHas{Audio,Video}..
   bool IsReady();
 
+  bool IsWaitingOnCDMResource();
+
   // Returns true if any of the decoders managed by this track buffer
   // contain aTime in their buffered ranges.
   bool ContainsTime(int64_t aTime, int64_t aTolerance);
 
   void BreakCycles();
 
   // Run MSE Reset Parser State Algorithm.
   // 3.5.2 Reset Parser State
@@ -180,20 +182,16 @@ private:
   // invoking Shutdown. This is all so that we can avoid destroying the decoders
   // off-main-thread. :-(
   nsTArray<nsRefPtr<SourceBufferDecoder>> mShutdownDecoders;
 
   // Contains only the initialized decoders managed by this TrackBuffer.
   // Access protected by mParentDecoder's monitor.
   nsTArray<nsRefPtr<SourceBufferDecoder>> mInitializedDecoders;
 
-  // Decoders which are waiting on a Content Decryption Module to be able to
-  // finish ReadMetadata.
-  nsTArray<nsRefPtr<SourceBufferDecoder>> mWaitingDecoders;
-
   // The decoder that the owning SourceBuffer is currently appending data to.
   // Modified on the main thread only.
   nsRefPtr<SourceBufferDecoder> mCurrentDecoder;
 
   nsRefPtr<MediaSourceDecoder> mParentDecoder;
   const nsCString mType;
 
   // The last start and end timestamps added to the TrackBuffer via
@@ -201,16 +199,19 @@ private:
   int64_t mLastStartTimestamp;
   Maybe<int64_t> mLastEndTimestamp;
   void AdjustDecodersTimestampOffset(int32_t aOffset);
 
   // The timestamp offset used by our current decoder, in microseconds.
   int64_t mLastTimestampOffset;
   int64_t mAdjustedTimestamp;
 
+  // True if at least one of our decoders has encrypted content.
+  bool mIsWaitingOnCDM;
+
   // Set when the first decoder used by this TrackBuffer is initialized.
   // Protected by mParentDecoder's monitor.
   MediaInfo mInfo;
 
   void ContinueShutdown();
   MediaPromiseHolder<ShutdownPromise> mShutdownPromise;
   bool mDecoderPerSegment;
   bool mShutdown;
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -365,18 +365,16 @@ skip-if = (toolkit == 'android' && proce
 [test_currentTime.html]
 [test_decode_error.html]
 [test_decoder_disable.html]
 [test_defaultMuted.html]
 [test_delay_load.html]
 skip-if = buildapp == 'b2g' && toolkit != 'gonk' # bug 1082984
 [test_dormant_playback.html]
 skip-if = (os == 'win' && os_version == '5.1') || (os != 'win' && toolkit != 'gonk')
-[test_eme_access_control.html]
-skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # bug 1043403, bug 1057908
 [test_eme_canvas_blocked.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s || (os == 'win' && !debug) # bug 1043403, bug 1057908, bug 1140675
 [test_eme_non_mse_fails.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # bug 1043403, bug 1057908
 #[test_eme_obs_notification.html]
 #skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # bug 1043403, bug 1057908
 # Disabled (bug 1140778) since this test fails and we don't want to remove the
 # functionality being tested by this test. We should still test other observers
deleted file mode 100644
--- a/dom/media/test/test_eme_access_control.html
+++ /dev/null
@@ -1,112 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test EME blocked cross-origin</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-  <script type="text/javascript" src="manifest.js"></script>
-  <script type="text/javascript" src="eme.js"></script>
-</head>
-<body>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-var manager = new MediaTestManager;
-
-function TestNoCORS(test, token)
-{
-  var token = token + "_nocors";
-
-  manager.started(token);
-
-  var v = document.createElement("video");
-
-  v.addEventListener("encrypted", function(ev) {
-    is(ev.initDataType, "", "initDataType should be empty for CORS cross-origin media");
-    is(ev.initData, null, "initData should be null for CORS cross-origin media");
-
-    manager.finished(token);
-  });
-
-  v.addEventListener("error", function() {
-    ok(false, "Should not receive error loading cross-origin media without crossorigin attribute");
-  });
-
-  v.src = test.uri;
-}
-
-function TestCORSFailure(test, token)
-{
-  var token = token + "_corsfail";
-
-  manager.started(token);
-
-  var v = document.createElement("video");
-  v.crossOrigin = true;
-
-  v.addEventListener("error", function(ev) {
-    ok(true, "Should get error loading cross-origin media");
-    manager.finished(token);
-  });
-
-  v.addEventListener("encrypted", function() {
-    ok(false, "Should not receive encrypted event loading cross-origin media");
-  });
-
-  v.src = test.uri;
-}
-
-function TestCORSSuccess(test, token)
-{
-  var token = token + "_corsok";
-
-  manager.started(token);
-
-  var v = document.createElement("video");
-  v.crossOrigin = true;
-
-  v.addEventListener("error", function(ev) {
-    ok(false, "Should not get error loading cross-origin media");
-  });
-
-  v.addEventListener("encrypted", function(ev) {
-    ok(ev.initData.byteLength > 0, "Should get encryption initData loading cross-origin media");
-    is(ev.initDataType, "cenc", "Should get correct encryption initDataType loading cross-origin media");
-    manager.finished(token);
-  });
-
-  v.src = test.uri;
-}
-
-function startTest(test, token)
-{
-  test.uri = "http://test1.mochi.test:8888/tests/dom/media/test/" + test.name;
-  TestNoCORS(test, token);
-  TestCORSFailure(test, token);
-
-  test.uri = "http://test1.mochi.test:8888/tests/dom/media/test/allowed.sjs?" + test.name;
-  TestCORSSuccess(test, token);
-}
-
-function beginTest() {
-  manager.runTests(gEMETests.filter(t => t.crossOrigin), startTest);
-}
-
-var prefs = [
-  [ "media.mediasource.enabled", true ],
-  [ "media.mediasource.whitelist", false ],
-  [ "media.mediasource.mp4.enabled", true ],
-];
-
-if (/Linux/.test(navigator.userAgent) ||
-    !document.createElement('video').canPlayType("video/mp4")) {
-  // XXX remove once we have mp4 PlatformDecoderModules on all platforms.
-  prefs.push([ "media.fragmented-mp4.exposed", true ]);
-  prefs.push([ "media.fragmented-mp4.use-blank-decoder", true ]);
-}
-
-SimpleTest.waitForExplicitFinish();
-SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
-</script>
-</pre>
-</body>
-</html>
--- a/dom/media/test/test_eme_non_mse_fails.html
+++ b/dom/media/test/test_eme_non_mse_fails.html
@@ -35,27 +35,29 @@ function DoSetMediaKeys(v)
 }
 
 function TestSetMediaKeys(test, token)
 {
   manager.started(token);
 
   var v = document.createElement("video");
 
-  // XXX the encrypted event should never fire here after bug 1134434
   v.addEventListener("encrypted", function() {
-    DoSetMediaKeys(v)
+    ok(false, token + " should not fire encrypted event");
+  });
 
-    .then(function() {
-      ok(false, token + " expected setMediaKeys to fail.");
-      manager.finished(token);
-    }, function(err) {
-      is(err.name, "NotSupportedError", token + " should return correct error");
-      manager.finished(token);
-    });
+  var loadedMetadata = false;
+  v.addEventListener("loadedmetadata", function() {
+    loadedMetadata = true;
+  });
+
+  v.addEventListener("error", function() {
+    ok(true, token + " expected error event");
+    ok(loadedMetadata, token + " expected loadedmetadata to have fired");
+    manager.finished(token);
   });
 
   v.src = test.name;
 }
 
 function TestSetSrc(test, token)
 {
   manager.started(token);
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -239,16 +239,19 @@ RespondWithHandler::ResolvedCallback(JSC
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
     }
 
     nsCOMPtr<nsIEventTarget> stsThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
     if (NS_WARN_IF(!stsThread)) {
       return;
     }
+
+    // XXXnsm, Fix for Bug 1141332 means that if we decide to make this
+    // streaming at some point, we'll need a different solution to that bug.
     rv = NS_AsyncCopy(body, responseBody, stsThread, NS_ASYNCCOPY_VIA_READSEGMENTS, 4096,
                       RespondWithCopyComplete, closure.forget());
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
     }
   } else {
     RespondWithCopyComplete(closure.forget(), NS_OK);
   }
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/deliver-gzip.sjs
@@ -0,0 +1,17 @@
+function handleRequest(request, response) {
+  // The string "hello" repeated 10 times followed by newline. Compressed using gzip.
+  var bytes = [0x1f, 0x8b, 0x08, 0x08, 0x4d, 0xe2, 0xf9, 0x54, 0x00, 0x03, 0x68,
+               0x65, 0x6c, 0x6c, 0x6f, 0x00, 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0xcf,
+               0x20, 0x85, 0xe0, 0x02, 0x00, 0xf5, 0x4b, 0x38, 0xcf, 0x33, 0x00,
+               0x00, 0x00];
+
+  response.setHeader("Content-Encoding", "gzip", false);
+  response.setHeader("Content-Length", "" + bytes.length, false);
+  response.setHeader("Content-Type", "text/plain", false);
+
+  var bos = Components.classes["@mozilla.org/binaryoutputstream;1"]
+      .createInstance(Components.interfaces.nsIBinaryOutputStream);
+  bos.setOutputStream(response.bodyOutputStream);
+
+  bos.writeByteArray(bytes, bytes.length);
+}
--- a/dom/workers/test/serviceworkers/fetch/fetch_tests.js
+++ b/dom/workers/test/serviceworkers/fetch/fetch_tests.js
@@ -66,8 +66,40 @@ fetch('nonresponse2.txt', null, function
   finish();
 });
 
 fetch('headers.txt', function(xhr) {
   my_ok(xhr.status == 200, "load should be successful");
   my_ok(xhr.responseText == "1", "request header checks should have passed");
   finish();
 }, null, [["X-Test1", "header1"], ["X-Test2", "header2"]]);
+
+var expectedUncompressedResponse = "";
+for (var i = 0; i < 10; ++i) {
+  expectedUncompressedResponse += "hello";
+}
+expectedUncompressedResponse += "\n";
+
+// ServiceWorker does not intercept, at which point the network request should
+// be correctly decoded.
+fetch('deliver-gzip.sjs', function(xhr) {
+  my_ok(xhr.status == 200, "network gzip load should be successful");
+  my_ok(xhr.responseText == expectedUncompressedResponse, "network gzip load should have synthesized response.");
+  my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "network Content-Encoding should be gzip.");
+  my_ok(xhr.getResponseHeader("Content-Length") == "35", "network Content-Length should be of original gzipped file.");
+  finish();
+});
+
+fetch('hello.gz', function(xhr) {
+  my_ok(xhr.status == 200, "gzip load should be successful");
+  my_ok(xhr.responseText == expectedUncompressedResponse, "gzip load should have synthesized response.");
+  my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "Content-Encoding should be gzip.");
+  my_ok(xhr.getResponseHeader("Content-Length") == "35", "Content-Length should be of original gzipped file.");
+  finish();
+});
+
+fetch('hello-after-extracting.gz', function(xhr) {
+  my_ok(xhr.status == 200, "gzip load should be successful");
+  my_ok(xhr.responseText == expectedUncompressedResponse, "gzip load should have synthesized response.");
+  my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "Content-Encoding should be gzip.");
+  my_ok(xhr.getResponseHeader("Content-Length") == "35", "Content-Length should be of original gzipped file.");
+  finish();
+});
--- a/dom/workers/test/serviceworkers/fetch_event_worker.js
+++ b/dom/workers/test/serviceworkers/fetch_event_worker.js
@@ -91,9 +91,28 @@ onfetch = function(ev) {
     ));
   }
 
   else if (ev.request.url.contains("nonexistent_imported_script.js")) {
     ev.respondWith(Promise.resolve(
       new Response("check_intercepted_script();", {})
     ));
   }
+
+  else if (ev.request.url.contains("deliver-gzip")) {
+    // Don't handle the request, this will make Necko perform a network request, at
+    // which point SetApplyConversion must be re-enabled, otherwise the request
+    // will fail.
+    return;
+  }
+
+  else if (ev.request.url.contains("hello.gz")) {
+    ev.respondWith(fetch("fetch/deliver-gzip.sjs"));
+  }
+
+  else if (ev.request.url.contains("hello-after-extracting.gz")) {
+    ev.respondWith(fetch("fetch/deliver-gzip.sjs").then(function(res) {
+      return res.text().then(function(body) {
+        return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
+      });
+    }));
+  }
 }
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -20,16 +20,17 @@ support-files =
   match_all_worker.js
   match_all_advanced_worker.js
   worker_unregister.js
   worker_update.js
   message_posting_worker.js
   fetch/index.html
   fetch/fetch_worker_script.js
   fetch/fetch_tests.js
+  fetch/deliver-gzip.sjs
   fetch/https/index.html
   fetch/https/register.html
   fetch/https/unregister.html
   fetch/https/https_test.js
   fetch/https/clonedresponse/index.html
   fetch/https/clonedresponse/register.html
   fetch/https/clonedresponse/unregister.html
   fetch/https/clonedresponse/https_test.js
--- a/gfx/layers/IMFYCbCrImage.cpp
+++ b/gfx/layers/IMFYCbCrImage.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "IMFYCbCrImage.h"
 #include "mozilla/layers/TextureD3D11.h"
 #include "mozilla/layers/CompositableClient.h"
 #include "mozilla/layers/CompositableForwarder.h"
 #include "mozilla/gfx/Types.h"
 #include "mozilla/layers/TextureClient.h"
+#include "d3d9.h"
 
 namespace mozilla {
 namespace layers {
 
 IMFYCbCrImage::IMFYCbCrImage(IMFMediaBuffer* aBuffer, IMF2DBuffer* a2DBuffer)
   : PlanarYCbCrImage(nullptr)
   , mBuffer(aBuffer)
   , m2DBuffer(a2DBuffer)
@@ -50,22 +51,171 @@ struct AutoLockTexture
     if (FAILED(hr)) {
       NS_WARNING("Failed to unlock the texture");
     }
   }
 
   RefPtr<IDXGIKeyedMutex> mMutex;
 };
 
+static TemporaryRef<IDirect3DTexture9>
+InitTextures(IDirect3DDevice9* aDevice,
+             const IntSize &aSize,
+            _D3DFORMAT aFormat,
+            RefPtr<IDirect3DSurface9>& aSurface,
+            HANDLE& aHandle,
+            D3DLOCKED_RECT& aLockedRect)
+{
+  if (!aDevice) {
+    return nullptr;
+  }
+
+  RefPtr<IDirect3DTexture9> result;
+  if (FAILED(aDevice->CreateTexture(aSize.width, aSize.height,
+                                    1, 0, aFormat, D3DPOOL_DEFAULT,
+                                    byRef(result), &aHandle))) {
+    return nullptr;
+  }
+  if (!result) {
+    return nullptr;
+  }
+
+  RefPtr<IDirect3DTexture9> tmpTexture;
+  if (FAILED(aDevice->CreateTexture(aSize.width, aSize.height,
+                                    1, 0, aFormat, D3DPOOL_SYSTEMMEM,
+                                    byRef(tmpTexture), nullptr))) {
+    return nullptr;
+  }
+  if (!tmpTexture) {
+    return nullptr;
+  }
+
+  tmpTexture->GetSurfaceLevel(0, byRef(aSurface));
+  aSurface->LockRect(&aLockedRect, nullptr, 0);
+  if (!aLockedRect.pBits) {
+    NS_WARNING("Could not lock surface");
+    return nullptr;
+  }
+
+  return result;
+}
+
+static void
+FinishTextures(IDirect3DDevice9* aDevice,
+               IDirect3DTexture9* aTexture,
+               IDirect3DSurface9* aSurface)
+{
+  if (!aDevice) {
+    return;
+  }
+
+  aSurface->UnlockRect();
+  nsRefPtr<IDirect3DSurface9> dstSurface;
+  aTexture->GetSurfaceLevel(0, getter_AddRefs(dstSurface));
+  aDevice->UpdateSurface(aSurface, nullptr, dstSurface, nullptr);
+}
+
+static bool UploadData(IDirect3DDevice9* aDevice,
+                       RefPtr<IDirect3DTexture9>& aTexture,
+                       HANDLE& aHandle,
+                       uint8_t* aSrc,
+                       const gfx::IntSize& aSrcSize,
+                       int32_t aSrcStride)
+{
+  RefPtr<IDirect3DSurface9> surf;
+  D3DLOCKED_RECT rect;
+  aTexture = InitTextures(aDevice, aSrcSize, D3DFMT_A8, surf, aHandle, rect);
+  if (!aTexture) {
+    return false;
+  }
+
+  if (aSrcStride == rect.Pitch) {
+    memcpy(rect.pBits, aSrc, rect.Pitch * aSrcSize.height);
+  } else {
+    for (int i = 0; i < aSrcSize.height; i++) {
+      memcpy((uint8_t*)rect.pBits + i * rect.Pitch,
+             aSrc + i * aSrcStride,
+             aSrcSize.width);
+    }
+  }
+
+  FinishTextures(aDevice, aTexture, surf);
+  return true;
+}
+
+TextureClient*
+IMFYCbCrImage::GetD3D9TextureClient(CompositableClient* aClient)
+{
+  IDirect3DDevice9* device = gfxWindowsPlatform::GetPlatform()->GetD3D9Device();
+
+  RefPtr<IDirect3DTexture9> textureY;
+  HANDLE shareHandleY = 0;
+  if (!UploadData(device, textureY, shareHandleY,
+                  mData.mYChannel, mData.mYSize, mData.mYStride)) {
+    return nullptr;
+  }
+
+  RefPtr<IDirect3DTexture9> textureCb;
+  HANDLE shareHandleCb = 0;
+  if (!UploadData(device, textureCb, shareHandleCb,
+                  mData.mCbChannel, mData.mCbCrSize, mData.mCbCrStride)) {
+    return nullptr;
+  }
+
+  RefPtr<IDirect3DTexture9> textureCr;
+  HANDLE shareHandleCr = 0;
+  if (!UploadData(device, textureCr, shareHandleCr,
+                  mData.mCrChannel, mData.mCbCrSize, mData.mCbCrStride)) {
+    return nullptr;
+  }
+
+  RefPtr<IDirect3DQuery9> query;
+  HRESULT hr = device->CreateQuery(D3DQUERYTYPE_EVENT, byRef(query));
+  hr = query->Issue(D3DISSUE_END);
+
+  int iterations = 0;
+  bool valid = false;
+  while (iterations < 10) {
+    HRESULT hr = query->GetData(nullptr, 0, D3DGETDATA_FLUSH);
+    if (hr == S_FALSE) {
+      Sleep(1);
+      iterations++;
+      continue;
+    }
+    if (hr == S_OK) {
+      valid = true;
+    }
+    break;
+  }
+
+  if (!valid) {
+    return nullptr;
+  }
+
+  RefPtr<DXGIYCbCrTextureClient> texClient =
+    new DXGIYCbCrTextureClient(aClient->GetForwarder(), TextureFlags::DEFAULT);
+  texClient->InitWith(textureY, textureCb, textureCr,
+                      shareHandleY, shareHandleCb, shareHandleCr,
+                      GetSize(), mData.mYSize, mData.mCbCrSize);
+  mTextureClient = texClient;
+
+  return mTextureClient;
+}
+
 TextureClient*
 IMFYCbCrImage::GetTextureClient(CompositableClient* aClient)
 {
   ID3D11Device* device = gfxWindowsPlatform::GetPlatform()->GetD3D11MediaDevice();
   if (!device ||
     aClient->GetForwarder()->GetCompositorBackendType() != LayersBackend::LAYERS_D3D11) {
+
+    IDirect3DDevice9* d3d9device = gfxWindowsPlatform::GetPlatform()->GetD3D9Device();
+    if (d3d9device && aClient->GetForwarder()->GetCompositorBackendType() == LayersBackend::LAYERS_D3D9) {
+      return GetD3D9TextureClient(aClient);
+    }
     return nullptr;
   }
 
   if (mTextureClient) {
     return mTextureClient;
   }
 
   RefPtr<ID3D11DeviceContext> ctx;
@@ -99,19 +249,35 @@ IMFYCbCrImage::GetTextureClient(Composit
     ctx->UpdateSubresource(textureY, 0, nullptr, mData.mYChannel,
                            mData.mYStride, mData.mYStride * mData.mYSize.height);
     ctx->UpdateSubresource(textureCb, 0, nullptr, mData.mCbChannel,
                            mData.mCbCrStride, mData.mCbCrStride * mData.mCbCrSize.height);
     ctx->UpdateSubresource(textureCr, 0, nullptr, mData.mCrChannel,
                            mData.mCbCrStride, mData.mCbCrStride * mData.mCbCrSize.height);
   }
 
-  RefPtr<DXGIYCbCrTextureClientD3D11> texClient =
-    new DXGIYCbCrTextureClientD3D11(aClient->GetForwarder(), TextureFlags::DEFAULT);
-  texClient->InitWith(textureY, textureCb, textureCr, GetSize());
+  RefPtr<IDXGIResource> resource;
+
+  HANDLE shareHandleY;
+  textureY->QueryInterface((IDXGIResource**)byRef(resource));
+  hr = resource->GetSharedHandle(&shareHandleY);
+
+  HANDLE shareHandleCb;
+  textureCb->QueryInterface((IDXGIResource**)byRef(resource));
+  hr = resource->GetSharedHandle(&shareHandleCb);
+
+  HANDLE shareHandleCr;
+  textureCr->QueryInterface((IDXGIResource**)byRef(resource));
+  hr = resource->GetSharedHandle(&shareHandleCr);
+
+  RefPtr<DXGIYCbCrTextureClient> texClient =
+    new DXGIYCbCrTextureClient(aClient->GetForwarder(), TextureFlags::DEFAULT);
+  texClient->InitWith(textureY, textureCb, textureCr,
+                      shareHandleY, shareHandleCb, shareHandleCr,
+                      GetSize(), mData.mYSize, mData.mCbCrSize);
   mTextureClient = texClient;
 
   return mTextureClient;
 }
 
 
 } /* layers */
 } /* mozilla */
--- a/gfx/layers/IMFYCbCrImage.h
+++ b/gfx/layers/IMFYCbCrImage.h
@@ -23,16 +23,18 @@ public:
   virtual TextureClient* GetTextureClient(CompositableClient* aClient) override;
 
 protected:
   virtual uint8_t* AllocateBuffer(uint32_t aSize) override {
     MOZ_CRASH("Can't do manual allocations with IMFYCbCrImage");
     return nullptr;
   }
 
+  TextureClient* GetD3D9TextureClient(CompositableClient* aClient);
+
   ~IMFYCbCrImage();
 
   RefPtr<IMFMediaBuffer> mBuffer;
   RefPtr<IMF2DBuffer> m2DBuffer;
   RefPtr<TextureClient> mTextureClient;
 };
 
 } // namepace layers
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -136,28 +136,33 @@ public:
     StartPanning,
     /**
      * APZ finished processing a touch.
      * |aArg| is 1 if touch was a click, 0 otherwise.
      */
     EndTouch,
     APZStateChangeSentinel
   };
-
   /**
    * General notices of APZ state changes for consumers.
    * |aGuid| identifies the APZC originating the state change.
    * |aChange| identifies the type of state change
    * |aArg| is used by some state changes to pass extra information (see
    *        the documentation for each state change above)
    */
   virtual void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
                                     APZStateChange aChange,
                                     int aArg = 0) {}
 
+  /**
+   * Notify content of a MozMouseScrollFailed event.
+   */
+  virtual void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
+  {}
+
   GeckoContentController() {}
   virtual void Destroy() {}
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~GeckoContentController() {}
 };
 
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -801,24 +801,68 @@ APZCTreeManager::TransformCoordinateToGe
   if (apzc && aOutTransformedPoint) {
     Matrix4x4 transformToApzc = GetScreenToApzcTransform(apzc);
     Matrix4x4 transformToGecko = GetApzcToGeckoTransform(apzc);
     Matrix4x4 outTransform = transformToApzc * transformToGecko;
     *aOutTransformedPoint = TransformTo<LayoutDevicePixel>(outTransform, aPoint);
   }
 }
 
+void
+APZCTreeManager::UpdateWheelTransaction(WidgetInputEvent& aEvent)
+{
+  WheelBlockState* txn = mInputQueue->GetCurrentWheelTransaction();
+  if (!txn) {
+    return;
+  }
+
+  // If the transaction has simply timed out, we don't need to do anything
+  // else.
+  if (txn->MaybeTimeout(TimeStamp::Now())) {
+    return;
+  }
+
+  switch (aEvent.message) {
+   case NS_MOUSE_MOVE:
+   case NS_DRAGDROP_OVER: {
+     WidgetMouseEvent* mouseEvent = aEvent.AsMouseEvent();
+     if (!mouseEvent->IsReal()) {
+       return;
+     }
+
+     ScreenIntPoint point =
+       ViewAs<ScreenPixel>(aEvent.refPoint, PixelCastJustification::LayoutDeviceToScreenForUntransformedEvent);
+     txn->OnMouseMove(point);
+     return;
+   }
+   case NS_KEY_PRESS:
+   case NS_KEY_UP:
+   case NS_KEY_DOWN:
+   case NS_MOUSE_BUTTON_UP:
+   case NS_MOUSE_BUTTON_DOWN:
+   case NS_MOUSE_DOUBLECLICK:
+   case NS_MOUSE_CLICK:
+   case NS_CONTEXTMENU:
+   case NS_DRAGDROP_DROP:
+     txn->EndTransaction();
+     return;
+  }
+}
+
 nsEventStatus
 APZCTreeManager::ProcessEvent(WidgetInputEvent& aEvent,
                               ScrollableLayerGuid* aOutTargetGuid,
                               uint64_t* aOutInputBlockId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   nsEventStatus result = nsEventStatus_eIgnore;
 
+  // Note, we call this before having transformed the reference point.
+  UpdateWheelTransaction(aEvent);
+
   // Transform the refPoint.
   // If the event hits an overscrolled APZC, instruct the caller to ignore it.
   HitTestResult hitResult = HitNothing;
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(ScreenPoint(aEvent.refPoint.x, aEvent.refPoint.y),
                                                         &hitResult);
   if (apzc) {
     MOZ_ASSERT(hitResult == HitLayer || hitResult == HitDispatchToContentRegion);
     apzc->GetGuid(aOutTargetGuid);
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -431,16 +431,17 @@ private:
                                   ScrollableLayerGuid* aOutTargetGuid,
                                   uint64_t* aOutInputBlockId);
   nsEventStatus ProcessWheelEvent(WidgetWheelEvent& aEvent,
                                   ScrollableLayerGuid* aOutTargetGuid,
                                   uint64_t* aOutInputBlockId);
   nsEventStatus ProcessEvent(WidgetInputEvent& inputEvent,
                              ScrollableLayerGuid* aOutTargetGuid,
                              uint64_t* aOutInputBlockId);
+  void UpdateWheelTransaction(WidgetInputEvent& aEvent);
   void UpdateZoomConstraintsRecursively(HitTestingTreeNode* aNode,
                                         const ZoomConstraints& aConstraints);
   void FlushRepaintsRecursively(HitTestingTreeNode* aNode);
 
   already_AddRefed<HitTestingTreeNode> RecycleOrCreateNode(TreeBuildingState& aState,
                                                            AsyncPanZoomController* aApzc);
   HitTestingTreeNode* PrepareNodeForLayer(const LayerMetricsWrapper& aLayer,
                                           const FrameMetrics& aMetrics,
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -677,17 +677,21 @@ public:
    : AsyncPanZoomAnimation(TimeDuration::FromMilliseconds(
                            gfxPrefs::APZSmoothScrollRepaintInterval()))
    , mApzc(aApzc)
    , mXAxisModel(aInitialPosition.x, aDestination.x, aInitialVelocity.x,
                  aSpringConstant, aDampingRatio)
    , mYAxisModel(aInitialPosition.y, aDestination.y, aInitialVelocity.y,
                  aSpringConstant, aDampingRatio)
    , mSource(aSource)
+   , mAllowOverscroll(true)
   {
+    if (mSource == ScrollSource::Wheel) {
+      mAllowOverscroll = mApzc.AllowScrollHandoffInWheelTransaction();
+    }
   }
 
   /**
    * Advances a smooth scroll simulation based on the time passed in |aDelta|.
    * This should be called whenever sampling the content transform for this
    * frame. Returns true if the smooth scroll should be advanced by one frame,
    * or false if the smooth scroll has ended.
    */
@@ -734,18 +738,17 @@ public:
                                 forceVerticalOverscroll);
 
     aFrameMetrics.ScrollBy(adjustedOffset / zoom);
 
     // The smooth scroll may have caused us to reach the end of our scroll range.
     // This can happen if either the layout.css.scroll-behavior.damping-ratio
     // preference is set to less than 1 (underdamped) or if a smooth scroll
     // inherits velocity from a fling gesture.
-    if (!IsZero(overscroll)) {
-
+    if (!IsZero(overscroll) && mAllowOverscroll) {
       // Hand off a fling with the remaining momentum to the next APZC in the
       // overscroll handoff chain.
 
       // We may have reached the end of the scroll range along one axis but
       // not the other. In such a case we only want to hand off the relevant
       // component of the fling.
       if (FuzzyEqualsAdditive(overscroll.x, 0.0f, COORDINATE_EPSILON)) {
         velocity.x = 0;
@@ -773,16 +776,17 @@ public:
     }
 
     return true;
   }
 private:
   AsyncPanZoomController& mApzc;
   AxisPhysicsMSDModel mXAxisModel, mYAxisModel;
   ScrollSource mSource;
+  bool mAllowOverscroll;
 };
 
 void
 AsyncPanZoomController::SetFrameTime(const TimeStamp& aTime) {
   sFrameTime = aTime;
 }
 
 /*static*/ void
@@ -1413,30 +1417,83 @@ AsyncPanZoomController::ConvertToGecko(c
       ReentrantMonitorAutoEnter lock(mMonitor);
       *aOut = layoutPoint / mFrameMetrics.GetDevPixelsPerCSSPixel();
     }
     return true;
   }
   return false;
 }
 
-nsEventStatus AsyncPanZoomController::OnScrollWheel(const ScrollWheelInput& aEvent)
+void
+AsyncPanZoomController::GetScrollWheelDelta(const ScrollWheelInput& aEvent,
+                                            double& aOutDeltaX,
+                                            double& aOutDeltaY) const
 {
-  double deltaX = aEvent.mDeltaX;
-  double deltaY = aEvent.mDeltaY;
+  ReentrantMonitorAutoEnter lock(mMonitor);
+
+  aOutDeltaX = aEvent.mDeltaX;
+  aOutDeltaY = aEvent.mDeltaY;
   switch (aEvent.mDeltaType) {
     case ScrollWheelInput::SCROLLDELTA_LINE: {
       LayoutDeviceIntSize scrollAmount = mFrameMetrics.GetLineScrollAmount();
-      deltaX *= scrollAmount.width;
-      deltaY *= scrollAmount.height;
+      aOutDeltaX *= scrollAmount.width;
+      aOutDeltaY *= scrollAmount.height;
       break;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("unexpected scroll delta type");
-      return nsEventStatus_eConsumeNoDefault;
+  }
+}
+
+// Return whether or not the underlying layer can be scrolled on either axis.
+bool
+AsyncPanZoomController::CanScroll(const ScrollWheelInput& aEvent) const
+{
+  double deltaX, deltaY;
+  GetScrollWheelDelta(aEvent, deltaX, deltaY);
+
+  if (!deltaX && !deltaY) {
+    return false;
+  }
+
+  return CanScroll(deltaX, deltaY);
+}
+
+bool
+AsyncPanZoomController::CanScroll(double aDeltaX, double aDeltaY) const
+{
+  ReentrantMonitorAutoEnter lock(mMonitor);
+  return mX.CanScroll(aDeltaX) || mY.CanScroll(aDeltaY);
+}
+
+bool
+AsyncPanZoomController::AllowScrollHandoffInWheelTransaction() const
+{
+  WheelBlockState* block = mInputQueue->CurrentWheelBlock();
+  return block->AllowScrollHandoff();
+}
+
+nsEventStatus AsyncPanZoomController::OnScrollWheel(const ScrollWheelInput& aEvent)
+{
+  double deltaX, deltaY;
+  GetScrollWheelDelta(aEvent, deltaX, deltaY);
+
+  if ((deltaX || deltaY) &&
+      !CanScroll(deltaX, deltaY) &&
+      mInputQueue->GetCurrentWheelTransaction())
+  {
+    // We can't scroll this apz anymore, so we simply drop the event.
+    if (gfxPrefs::MouseScrollTestingEnabled()) {
+      if (nsRefPtr<GeckoContentController> controller = GetGeckoContentController()) {
+        controller->NotifyMozMouseScrollEvent(
+          mFrameMetrics.GetScrollId(),
+          NS_LITERAL_STRING("MozMouseScrollFailed"));
+      }
+    }
+    return nsEventStatus_eConsumeNoDefault;
   }
 
   switch (aEvent.mScrollMode) {
     case ScrollWheelInput::SCROLLMODE_INSTANT: {
       // Decompose into pan events for simplicity.
       PanGestureInput start(PanGestureInput::PANGESTURE_START, aEvent.mTime, aEvent.mTimeStamp,
                             aEvent.mOrigin, ScreenPoint(0, 0), aEvent.modifiers);
       start.mLocalPanStartPoint = aEvent.mLocalOrigin;
@@ -1479,16 +1536,27 @@ nsEventStatus AsyncPanZoomController::On
       StartSmoothScroll(ScrollSource::Wheel);
       break;
     }
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
+void
+AsyncPanZoomController::NotifyMozMouseScrollEvent(const nsString& aString) const
+{
+  nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
+  if (!controller) {
+    return;
+  }
+
+  controller->NotifyMozMouseScrollEvent(mFrameMetrics.GetScrollId(), aString);
+}
+
 nsEventStatus AsyncPanZoomController::OnPanMayBegin(const PanGestureInput& aEvent) {
   APZC_LOG("%p got a pan-maybegin in state %d\n", this, mState);
 
   mX.StartTouch(aEvent.mLocalPanStartPoint.x, aEvent.mTime);
   mY.StartTouch(aEvent.mLocalPanStartPoint.y, aEvent.mTime);
   if (mPanGestureState) {
     mPanGestureState->GetOverscrollHandoffChain()->CancelAnimations();
   } else {
@@ -1720,16 +1788,29 @@ ScreenPoint AsyncPanZoomController::ToSc
   return TransformVector<ScreenPixel>(GetTransformToThis().Inverse(), aVector, aAnchor);
 }
 
 ParentLayerPoint AsyncPanZoomController::ToParentLayerCoordinates(const ScreenPoint& aVector,
                                                                   const ScreenPoint& aAnchor) const {
   return TransformVector<ParentLayerPixel>(GetTransformToThis(), aVector, aAnchor);
 }
 
+bool AsyncPanZoomController::Contains(const ScreenIntPoint& aPoint) const
+{
+  Matrix4x4 transformToThis = GetTransformToThis();
+  ParentLayerIntPoint point = TransformTo<ParentLayerPixel>(transformToThis, aPoint);
+
+  ParentLayerIntRect cb;
+  {
+    ReentrantMonitorAutoEnter lock(mMonitor);
+    GetFrameMetrics().mCompositionBounds.ToIntRect(&cb);
+  }
+  return cb.Contains(point);
+}
+
 ScreenCoord AsyncPanZoomController::PanDistance() const {
   ParentLayerPoint panVector;
   ParentLayerPoint panStart;
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
     panVector = ParentLayerPoint(mX.PanDistance(), mY.PanDistance());
     panStart = PanStart();
   }
@@ -1911,16 +1992,23 @@ bool AsyncPanZoomController::AttemptScro
     }
   }
 
   // If we consumed the entire displacement as a normal scroll, great.
   if (IsZero(overscroll)) {
     return true;
   }
 
+  // If in a wheel transaction that has not ended, we drop overscroll.
+  if (aOverscrollHandoffState.mScrollSource == ScrollSource::Wheel &&
+      !AllowScrollHandoffInWheelTransaction())
+  {
+    return true;
+  }
+
   // If there is overscroll, first try to hand it off to an APZC later
   // in the handoff chain to consume (either as a normal scroll or as
   // overscroll).
   // Note: "+ overscroll" rather than "- overscroll" because "overscroll"
   // is what's left of "displacement", and "displacement" is "start - end".
   ++aOverscrollHandoffState.mChainIndex;
   if (CallDispatchScroll(aEndPoint + overscroll, aEndPoint,
                          aOverscrollHandoffState)) {
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -356,16 +356,26 @@ public:
    * The anchor is necessary because with 3D tranforms, the location of the
    * vector can affect the result of the transform.
    * To respect the lock ordering, mMonitor must NOT be held when calling
    * this function (since this function acquires the tree lock).
    */
   ParentLayerPoint ToParentLayerCoordinates(const ScreenPoint& aVector,
                                             const ScreenPoint& aAnchor) const;
 
+  // Return whether or not a wheel event will be able to scroll in either
+  // direction.
+  bool CanScroll(const ScrollWheelInput& aEvent) const;
+
+  // Return whether or not a scroll delta will be able to scroll in either
+  // direction.
+  bool CanScroll(double aDeltaX, double aDeltaY) const;
+
+  void NotifyMozMouseScrollEvent(const nsString& aString) const;
+
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   ~AsyncPanZoomController();
 
   /**
    * Helper method for touches beginning. Sets everything up for panning and any
    * multitouch gestures.
    */
@@ -418,16 +428,20 @@ protected:
   nsEventStatus OnPanMomentumStart(const PanGestureInput& aEvent);
   nsEventStatus OnPanMomentumEnd(const PanGestureInput& aEvent);
 
   /**
    * Helper methods for handling scroll wheel events.
    */
   nsEventStatus OnScrollWheel(const ScrollWheelInput& aEvent);
 
+  void GetScrollWheelDelta(const ScrollWheelInput& aEvent,
+                           double& aOutDeltaX,
+                           double& aOutDeltaY) const;
+
   /**
    * Helper methods for long press gestures.
    */
   nsEventStatus OnLongPress(const TapGestureInput& aEvent);
   nsEventStatus OnLongPressUp(const TapGestureInput& aEvent);
 
   /**
    * Helper method for single tap gestures.
@@ -844,16 +858,19 @@ private:
                    const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                    bool aHandoff);
 
   // Start an overscroll animation with the given initial velocity.
   void StartOverscrollAnimation(const ParentLayerPoint& aVelocity);
 
   void StartSmoothScroll(ScrollSource aSource);
 
+  // Returns whether overscroll is allowed during a wheel event.
+  bool AllowScrollHandoffInWheelTransaction() const;
+
   /* ===================================================================
    * The functions and members in this section are used to make ancestor chains
    * out of APZC instances. These chains can only be walked or manipulated
    * while holding the lock in the associated APZCTreeManager instance.
    */
 public:
   void SetParent(AsyncPanZoomController* aParent) {
     mParent = aParent;
@@ -979,16 +996,20 @@ public:
   void SetAncestorTransform(const Matrix4x4& aTransformToLayer) {
     mAncestorTransform = aTransformToLayer;
   }
 
   Matrix4x4 GetAncestorTransform() const {
     return mAncestorTransform;
   }
 
+  // Returns whether or not this apzc contains the given screen point within
+  // its composition bounds.
+  bool Contains(const ScreenIntPoint& aPoint) const;
+
   bool IsOverscrolled() const {
     return mX.IsOverscrolled() || mY.IsOverscrolled();
   }
 
 private:
   /* This is the cumulative CSS transform for all the layers from (and including)
    * the parent APZC down to (but excluding) this one. */
   Matrix4x4 mAncestorTransform;
--- a/gfx/layers/apz/src/Axis.cpp
+++ b/gfx/layers/apz/src/Axis.cpp
@@ -389,16 +389,26 @@ void Axis::CancelTouch() {
     mVelocityQueue.RemoveElementAt(0);
   }
 }
 
 bool Axis::CanScroll() const {
   return GetPageLength() - GetCompositionLength() > COORDINATE_EPSILON;
 }
 
+bool Axis::CanScroll(double aDelta) const
+{
+  if (!CanScroll() || mAxisLocked) {
+    return false;
+  }
+
+  ParentLayerCoord delta = aDelta;
+  return DisplacementWillOverscrollAmount(delta) != delta;
+}
+
 bool Axis::CanScrollNow() const {
   return !mAxisLocked && CanScroll();
 }
 
 bool Axis::FlingApplyFrictionOrCancel(const TimeDuration& aDelta,
                                       float aFriction,
                                       float aThreshold) {
   if (fabsf(mVelocity) <= aThreshold) {
--- a/gfx/layers/apz/src/Axis.h
+++ b/gfx/layers/apz/src/Axis.h
@@ -143,16 +143,21 @@ public:
                                   float aThreshold);
 
   /**
    * Returns true if the page has room to be scrolled along this axis.
    */
   bool CanScroll() const;
 
   /**
+   * Returns whether this axis can scroll any more in a particular direction.
+   */
+  bool CanScroll(double aDelta) const;
+
+  /**
    * Returns true if the page has room to be scrolled along this axis
    * and this axis is not scroll-locked.
    */
   bool CanScrollNow() const;
 
   void SetAxisLocked(bool aAxisLocked) { mAxisLocked = aAxisLocked; }
 
   /**
--- a/gfx/layers/apz/src/InputBlockState.cpp
+++ b/gfx/layers/apz/src/InputBlockState.cpp
@@ -44,21 +44,27 @@ InputBlockState::SetConfirmedTargetApzc(
     return true;
   }
 
   // Log enabled by default for now, we will put it in a TBS_LOG eventually
   // once this code is more baked
   printf_stderr("%p replacing unconfirmed target %p with real target %p\n",
       this, mTargetApzc.get(), aTargetApzc.get());
 
+  UpdateTargetApzc(aTargetApzc);
+  return true;
+}
+
+void
+InputBlockState::UpdateTargetApzc(const nsRefPtr<AsyncPanZoomController>& aTargetApzc)
+{
   // note that aTargetApzc MAY be null here.
   mTargetApzc = aTargetApzc;
   mTransformToApzc = aTargetApzc ? aTargetApzc->GetTransformToThis() : gfx::Matrix4x4();
   mOverscrollHandoffChain = (mTargetApzc ? mTargetApzc->BuildOverscrollHandoffChain() : nullptr);
-  return true;
 }
 
 const nsRefPtr<AsyncPanZoomController>&
 InputBlockState::GetTargetApzc() const
 {
   return mTargetApzc;
 }
 
@@ -137,20 +143,73 @@ CancelableBlockState::IsReadyForHandling
 void
 CancelableBlockState::DispatchImmediate(const InputData& aEvent) const
 {
   MOZ_ASSERT(!HasEvents());
   MOZ_ASSERT(GetTargetApzc());
   GetTargetApzc()->HandleInputEvent(aEvent, mTransformToApzc);
 }
 
+// This is used to track the current wheel transaction.
+static uint64_t sLastWheelBlockId = InputBlockState::NO_BLOCK_ID;
+
 WheelBlockState::WheelBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc,
-                                 bool aTargetConfirmed)
+                                 bool aTargetConfirmed,
+                                 const ScrollWheelInput& aInitialEvent)
   : CancelableBlockState(aTargetApzc, aTargetConfirmed)
+  , mTransactionEnded(false)
 {
+  sLastWheelBlockId = GetBlockId();
+
+  if (aTargetConfirmed) {
+    // Find the nearest APZC in the overscroll handoff chain that is scrollable.
+    // If we get a content confirmation later that the apzc is different, then
+    // content should have found a scrollable apzc, so we don't need to handle
+    // that case.
+    nsRefPtr<AsyncPanZoomController> apzc =
+      mOverscrollHandoffChain->FindFirstScrollable(aInitialEvent);
+
+    // If nothing is scrollable, we don't consider this block as starting a
+    // transaction.
+    if (!apzc) {
+      EndTransaction();
+      return;
+    }
+
+    if (apzc != GetTargetApzc()) {
+      UpdateTargetApzc(apzc);
+    }
+  }
+}
+
+void
+WheelBlockState::Update(const ScrollWheelInput& aEvent)
+{
+  // We might not be in a transaction if the block never started in a
+  // transaction - for example, if nothing was scrollable.
+  if (!InTransaction()) {
+    return;
+  }
+
+  // If we can't scroll in the direction of the wheel event, we don't update
+  // the last move time. This allows us to timeout a transaction even if the
+  // mouse isn't moving.
+  //
+  // We skip this check if the target is not yet confirmed, so that when it is
+  // confirmed, we don't timeout the transaction.
+  nsRefPtr<AsyncPanZoomController> apzc = GetTargetApzc();
+  if (IsTargetConfirmed() && !apzc->CanScroll(aEvent)) {
+    return;
+  }
+
+  // Update the time of the last known good event, and reset the mouse move
+  // time to null. This will reset the delays on both the general transaction
+  // timeout and the mouse-move-in-frame timeout.
+  mLastEventTime = aEvent.mTimeStamp;
+  mLastMouseMove = TimeStamp();
 }
 
 void
 WheelBlockState::AddEvent(const ScrollWheelInput& aEvent)
 {
   mEvents.AppendElement(aEvent);
 }
 
@@ -185,25 +244,127 @@ WheelBlockState::HandleEvents()
     mEvents.RemoveElementAt(0);
     GetTargetApzc()->HandleInputEvent(event, mTransformToApzc);
   }
 }
 
 bool
 WheelBlockState::MustStayActive()
 {
-  return false;
+  return !mTransactionEnded;
 }
 
 const char*
 WheelBlockState::Type()
 {
   return "scroll wheel";
 }
 
+bool
+WheelBlockState::ShouldAcceptNewEvent() const
+{
+  if (!InTransaction()) {
+    // If we're not in a transaction, start a new one.
+    return false;
+  }
+  nsRefPtr<AsyncPanZoomController> apzc = GetTargetApzc();
+  if (!apzc || apzc->IsDestroyed()) {
+    return false;
+  }
+
+  return true;
+}
+
+bool
+WheelBlockState::MaybeTimeout(const ScrollWheelInput& aEvent)
+{
+  if (MaybeTimeout(aEvent.mTimeStamp)) {
+    return true;
+  }
+
+  if (!mLastMouseMove.IsNull()) {
+    // If there's a recent mouse movement, we can time out the transaction early.
+    TimeDuration duration = TimeStamp::Now() - mLastMouseMove;
+    if (duration.ToMilliseconds() >= gfxPrefs::MouseWheelIgnoreMoveDelayMs()) {
+      TBS_LOG("%p wheel transaction timed out after mouse move\n", this);
+      EndTransaction();
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool
+WheelBlockState::MaybeTimeout(const TimeStamp& aTimeStamp)
+{
+  // End the transaction if the event occurred > 1.5s after the most recently
+  // seen wheel event.
+  TimeDuration duration = aTimeStamp - mLastEventTime;
+  if (duration.ToMilliseconds() < gfxPrefs::MouseWheelTransactionTimeoutMs()) {
+    return false;
+  }
+
+  TBS_LOG("%p wheel transaction timed out\n", this);
+
+  if (gfxPrefs::MouseScrollTestingEnabled()) {
+    nsRefPtr<AsyncPanZoomController> apzc = GetTargetApzc();
+    apzc->NotifyMozMouseScrollEvent(NS_LITERAL_STRING("MozMouseScrollTransactionTimeout"));
+  }
+
+  EndTransaction();
+  return true;
+}
+
+void
+WheelBlockState::OnMouseMove(const ScreenIntPoint& aPoint)
+{
+  if (!GetTargetApzc()->Contains(aPoint)) {
+    EndTransaction();
+    return;
+  }
+
+  if (mLastMouseMove.IsNull()) {
+    // If the cursor is moving inside the frame, and it is more than the
+    // ignoremovedelay time since the last scroll operation, we record
+    // this as the most recent mouse movement.
+    TimeStamp now = TimeStamp::Now();
+    TimeDuration duration = now - mLastEventTime;
+    if (duration.ToMilliseconds() >= gfxPrefs::MouseWheelIgnoreMoveDelayMs()) {
+      mLastMouseMove = now;
+    }
+  }
+}
+
+bool
+WheelBlockState::InTransaction() const
+{
+  // We consider a wheel block to be in a transaction if it has a confirmed
+  // target and is the most recent wheel input block to be created.
+  if (GetBlockId() != sLastWheelBlockId) {
+    return false;
+  }
+  return !mTransactionEnded;
+}
+
+bool
+WheelBlockState::AllowScrollHandoff() const
+{
+  // If we're in a wheel transaction, we do not allow overscroll handoff until
+  // a new event ends the wheel transaction.
+  return !IsTargetConfirmed() || !InTransaction();
+}
+
+void
+WheelBlockState::EndTransaction()
+{
+  TBS_LOG("%p ending wheel transaction\n", this);
+  mTransactionEnded = true;
+}
+
 TouchBlockState::TouchBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc,
                                  bool aTargetConfirmed)
   : CancelableBlockState(aTargetApzc, aTargetConfirmed)
   , mAllowedTouchBehaviorSet(false)
   , mDuringFastMotion(false)
   , mSingleTapOccurred(false)
 {
   TBS_LOG("Creating %p\n", this);
--- a/gfx/layers/apz/src/InputBlockState.h
+++ b/gfx/layers/apz/src/InputBlockState.h
@@ -40,22 +40,26 @@ public:
 
   bool SetConfirmedTargetApzc(const nsRefPtr<AsyncPanZoomController>& aTargetApzc);
   const nsRefPtr<AsyncPanZoomController>& GetTargetApzc() const;
   const nsRefPtr<const OverscrollHandoffChain>& GetOverscrollHandoffChain() const;
   uint64_t GetBlockId() const;
 
   bool IsTargetConfirmed() const;
 
+protected:
+  void UpdateTargetApzc(const nsRefPtr<AsyncPanZoomController>& aTargetApzc);
+
 private:
   nsRefPtr<AsyncPanZoomController> mTargetApzc;
-  nsRefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
   bool mTargetConfirmed;
   const uint64_t mBlockId;
 protected:
+  nsRefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
+
   // Used to transform events from global screen space to |mTargetApzc|'s
   // screen space. It's cached at the beginning of the input block so that
   // all events in the block are in the same coordinate space.
   gfx::Matrix4x4 mTransformToApzc;
 };
 
 /**
  * This class represents a set of events that can be cancelled by web content
@@ -149,33 +153,87 @@ private:
 
 /**
  * A single block of wheel events.
  */
 class WheelBlockState : public CancelableBlockState
 {
 public:
   WheelBlockState(const nsRefPtr<AsyncPanZoomController>& aTargetApzc,
-                  bool aTargetConfirmed);
+                  bool aTargetConfirmed,
+                  const ScrollWheelInput& aEvent);
 
   bool IsReadyForHandling() const override;
   bool HasEvents() const override;
   void DropEvents() override;
   void HandleEvents() override;
   bool MustStayActive() override;
   const char* Type() override;
 
   void AddEvent(const ScrollWheelInput& aEvent);
 
   WheelBlockState *AsWheelBlock() override {
     return this;
   }
 
+  /**
+   * Determine whether this wheel block is accepting new events.
+   */
+  bool ShouldAcceptNewEvent() const;
+
+  /**
+   * Call to check whether a wheel event will cause the current transaction to
+   * timeout.
+   */
+  bool MaybeTimeout(const ScrollWheelInput& aEvent);
+
+  /**
+   * Called from APZCTM when a mouse move or drag+drop event occurs, before
+   * the event has been processed.
+   */
+  void OnMouseMove(const ScreenIntPoint& aPoint);
+
+  /**
+   * Returns whether or not the block is participating in a wheel transaction.
+   * This means that the block is the most recent input block to be created,
+   * and no events have occurred that would require scrolling a different
+   * frame.
+   *
+   * @return True if in a transaction, false otherwise.
+   */
+  bool InTransaction() const;
+
+  /**
+   * Mark the block as no longer participating in a wheel transaction. This
+   * will force future wheel events to begin a new input block.
+   */
+  void EndTransaction();
+
+  /**
+   * @return Whether or not overscrolling is prevented for this wheel block.
+   */
+  bool AllowScrollHandoff() const;
+
+  /**
+   * Called to check and possibly end the transaction due to a timeout.
+   *
+   * @return True if the transaction ended, false otherwise.
+   */
+  bool MaybeTimeout(const TimeStamp& aTimeStamp);
+
+  /**
+   * Update the wheel transaction state for a new event.
+   */
+  void Update(const ScrollWheelInput& aEvent);
+
 private:
   nsTArray<ScrollWheelInput> mEvents;
+  TimeStamp mLastEventTime;
+  TimeStamp mLastMouseMove;
+  bool mTransactionEnded;
 };
 
 /**
  * This class represents a single touch block. A touch block is
  * a set of touch events that can be cancelled by web content via
  * touch event listeners.
  *
  * Every touch-start event creates a new touch block. In this case, the
--- a/gfx/layers/apz/src/InputQueue.cpp
+++ b/gfx/layers/apz/src/InputQueue.cpp
@@ -155,39 +155,47 @@ nsEventStatus
 InputQueue::ReceiveScrollWheelInput(const nsRefPtr<AsyncPanZoomController>& aTarget,
                                     bool aTargetConfirmed,
                                     const ScrollWheelInput& aEvent,
                                     uint64_t* aOutInputBlockId) {
   WheelBlockState* block = nullptr;
   if (!mInputBlockQueue.IsEmpty()) {
     block = mInputBlockQueue.LastElement()->AsWheelBlock();
 
-    // If the block's APZC has been destroyed, request a new block.
-    if (block && block->GetTargetApzc()->IsDestroyed()) {
+    // If the block is not accepting new events we'll create a new input block
+    // (and therefore a new wheel transaction).
+    if (block &&
+        (!block->ShouldAcceptNewEvent() ||
+         block->MaybeTimeout(aEvent)))
+    {
       block = nullptr;
     }
   }
 
+  MOZ_ASSERT(!block || block->InTransaction());
+
   if (!block) {
-    block = new WheelBlockState(aTarget, aTargetConfirmed);
+    block = new WheelBlockState(aTarget, aTargetConfirmed, aEvent);
     INPQ_LOG("started new scroll wheel block %p for target %p\n", block, aTarget.get());
 
     SweepDepletedBlocks();
     mInputBlockQueue.AppendElement(block);
 
     CancelAnimationsForNewBlock(block);
     MaybeRequestContentResponse(aTarget, block);
   } else {
     INPQ_LOG("received new event in block %p\n", block);
   }
 
   if (aOutInputBlockId) {
     *aOutInputBlockId = block->GetBlockId();
   }
 
+  block->Update(aEvent);
+
   // Note that the |aTarget| the APZCTM sent us may contradict the confirmed
   // target set on the block. In this case the confirmed target (which may be
   // null) should take priority. This is equivalent to just always using the
   // target (confirmed or not) from the block, which is what
   // MaybeHandleCurrentBlock() does.
   if (!MaybeHandleCurrentBlock(block, aEvent)) {
     block->AddEvent(aEvent.AsScrollWheelInput());
   }
@@ -281,21 +289,42 @@ InputQueue::CurrentBlock() const
 
   MOZ_ASSERT(!mInputBlockQueue.IsEmpty());
   return mInputBlockQueue[0].get();
 }
 
 TouchBlockState*
 InputQueue::CurrentTouchBlock() const
 {
-  TouchBlockState *block = CurrentBlock()->AsTouchBlock();
+  TouchBlockState* block = CurrentBlock()->AsTouchBlock();
+  MOZ_ASSERT(block);
+  return block;
+}
+
+WheelBlockState*
+InputQueue::CurrentWheelBlock() const
+{
+  WheelBlockState* block = CurrentBlock()->AsWheelBlock();
   MOZ_ASSERT(block);
   return block;
 }
 
+WheelBlockState*
+InputQueue::GetCurrentWheelTransaction() const
+{
+  if (mInputBlockQueue.IsEmpty()) {
+    return nullptr;
+  }
+  WheelBlockState* block = CurrentBlock()->AsWheelBlock();
+  if (!block || !block->InTransaction()) {
+    return nullptr;
+  }
+  return block;
+}
+
 bool
 InputQueue::HasReadyTouchBlock() const
 {
   return !mInputBlockQueue.IsEmpty() &&
          mInputBlockQueue[0]->AsTouchBlock() &&
          mInputBlockQueue[0]->IsReadyForHandling();
 }
 
@@ -421,15 +450,15 @@ InputQueue::ProcessInputBlocks() {
       // next input block is started. Therefore we cannot remove the block from
       // the queue until we have started another block. This block will be
       // removed by SweepDeletedBlocks() whenever a new block is added.
       break;
     }
 
     // If we get here, we know there are more touch blocks in the queue after
     // |curBlock|, so we can remove |curBlock| and try to process the next one.
-    INPQ_LOG("discarding depleted touch block %p\n", curBlock);
+    INPQ_LOG("discarding processed %s block %p\n", curBlock->Type(), curBlock);
     mInputBlockQueue.RemoveElementAt(0);
   } while (!mInputBlockQueue.IsEmpty());
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/InputQueue.h
+++ b/gfx/layers/apz/src/InputQueue.h
@@ -18,16 +18,17 @@ class MultiTouchInput;
 class ScrollWheelInput;
 
 namespace layers {
 
 class AsyncPanZoomController;
 class OverscrollHandoffChain;
 class CancelableBlockState;
 class TouchBlockState;
+class WheelBlockState;
 
 /**
  * This class stores incoming input events, separated into "input blocks", until
  * they are ready for handling. Currently input blocks are only created from
  * touch input.
  */
 class InputQueue {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InputQueue)
@@ -75,25 +76,35 @@ public:
    * of the the newly-created block.
    */
   uint64_t InjectNewTouchBlock(AsyncPanZoomController* aTarget);
   /**
    * Returns the pending input block at the head of the queue.
    */
   CancelableBlockState* CurrentBlock() const;
   /**
-   * Returns the current pending input block as a touch block. It must only
+   * Returns the current pending input block as a touch block. It must only be
    * called if the current pending block is a touch block.
    */
   TouchBlockState* CurrentTouchBlock() const;
   /**
+   * Returns the current pending input block as a wheel block. It must only be
+   * called if the current pending block is a wheel block.
+   */
+  WheelBlockState* CurrentWheelBlock() const;
+  /**
    * Returns true iff the pending block at the head of the queue is ready for
    * handling.
    */
   bool HasReadyTouchBlock() const;
+  /**
+   * If there is a wheel transaction, returns the WheelBlockState representing
+   * the transaction. Otherwise, returns null.
+   */
+  WheelBlockState* GetCurrentWheelTransaction() const;
 
 private:
   ~InputQueue();
 
   TouchBlockState* StartNewTouchBlock(const nsRefPtr<AsyncPanZoomController>& aTarget,
                                       bool aTargetConfirmed,
                                       bool aCopyPropertiesFromCurrent);
 
--- a/gfx/layers/apz/src/OverscrollHandoffState.cpp
+++ b/gfx/layers/apz/src/OverscrollHandoffState.cpp
@@ -58,17 +58,16 @@ OverscrollHandoffChain::IndexOf(const As
     }
   }
   return i;
 }
 
 void
 OverscrollHandoffChain::ForEachApzc(APZCMethod aMethod) const
 {
-  MOZ_ASSERT(Length() > 0);
   for (uint32_t i = 0; i < Length(); ++i) {
     (mChain[i]->*aMethod)();
   }
 }
 
 bool
 OverscrollHandoffChain::AnyApzc(APZCPredicate aPredicate) const
 {
@@ -151,11 +150,21 @@ OverscrollHandoffChain::HasOverscrolledA
 }
 
 bool
 OverscrollHandoffChain::HasFastMovingApzc() const
 {
   return AnyApzc(&AsyncPanZoomController::IsMovingFast);
 }
 
+nsRefPtr<AsyncPanZoomController>
+OverscrollHandoffChain::FindFirstScrollable(const ScrollWheelInput& aInput) const
+{
+  for (size_t i = 0; i < Length(); i++) {
+    if (mChain[i]->CanScroll(aInput)) {
+      return mChain[i];
+    }
+  }
+  return nullptr;
+}
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/OverscrollHandoffState.h
+++ b/gfx/layers/apz/src/OverscrollHandoffState.h
@@ -108,16 +108,18 @@ public:
   bool CanBePanned(const AsyncPanZoomController* aApzc) const;
 
   // Determine whether any APZC along this handoff chain is overscrolled.
   bool HasOverscrolledApzc() const;
 
   // Determine whether any APZC along this handoff chain is moving fast.
   bool HasFastMovingApzc() const;
 
+  nsRefPtr<AsyncPanZoomController> FindFirstScrollable(const ScrollWheelInput& aInput) const;
+
 private:
   std::vector<nsRefPtr<AsyncPanZoomController>> mChain;
 
   typedef void (AsyncPanZoomController::*APZCMethod)();
   typedef bool (AsyncPanZoomController::*APZCPredicate)() const;
   void ForEachApzc(APZCMethod aMethod) const;
   bool AnyApzc(APZCPredicate aPredicate) const;
 };
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -661,11 +661,29 @@ APZCCallbackHelper::SendSetAllowedTouchB
 {
   nsTArray<TouchBehaviorFlags> flags;
   for (uint32_t i = 0; i < aEvent.touches.Length(); i++) {
     flags.AppendElement(widget::ContentHelper::GetAllowedTouchBehavior(aWidget, aEvent.touches[i]->mRefPoint));
   }
   aCallback->Run(aInputBlockId, flags);
 }
 
+void
+APZCCallbackHelper::NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
+{
+  nsCOMPtr<nsIContent> targetContent = nsLayoutUtils::FindContentFor(aScrollId);
+  if (!targetContent) {
+    return;
+  }
+  nsCOMPtr<nsIDocument> ownerDoc = targetContent->OwnerDoc();
+  if (!ownerDoc) {
+    return;
+  }
+
+  nsContentUtils::DispatchTrustedEvent(
+    ownerDoc, targetContent,
+    aEvent,
+    true, true);
+}
+
 }
 }
 
--- a/gfx/layers/apz/util/APZCCallbackHelper.h
+++ b/gfx/layers/apz/util/APZCCallbackHelper.h
@@ -176,14 +176,17 @@ public:
                                               const nsRefPtr<SetTargetAPZCCallback>& aCallback);
 
     /* Figure out the allowed touch behaviors of each touch point in |aEvent|
      * and send that information to the provided callback. */
     static void SendSetAllowedTouchBehaviorNotification(nsIWidget* aWidget,
                                                          const WidgetTouchEvent& aEvent,
                                                          uint64_t aInputBlockId,
                                                          const nsRefPtr<SetAllowedTouchBehaviorCallback>& aCallback);
+
+    /* Notify content of a mouse scroll testing event. */
+    static void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent);
 };
 
 }
 }
 
 #endif /* mozilla_layers_APZCCallbackHelper_h */
--- a/gfx/layers/apz/util/ChromeProcessController.cpp
+++ b/gfx/layers/apz/util/ChromeProcessController.cpp
@@ -194,9 +194,20 @@ ChromeProcessController::NotifyAPZStateC
         NewRunnableMethod(this, &ChromeProcessController::NotifyAPZStateChange,
                           aGuid, aChange, aArg));
     return;
   }
 
   mAPZEventState->ProcessAPZStateChange(GetDocument(), aGuid.mScrollId, aChange, aArg);
 }
 
+void
+ChromeProcessController::NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
+{
+  if (MessageLoop::current() != mUILoop) {
+    mUILoop->PostTask(
+      FROM_HERE,
+      NewRunnableMethod(this, &ChromeProcessController::NotifyMozMouseScrollEvent, aScrollId, aEvent));
+    return;
+  }
 
+  APZCCallbackHelper::NotifyMozMouseScrollEvent(aScrollId, aEvent);
+}
--- a/gfx/layers/apz/util/ChromeProcessController.h
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -50,16 +50,18 @@ public:
   virtual void HandleLongTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers,
                                const ScrollableLayerGuid& aGuid,
                                uint64_t aInputBlockId) override;
   virtual void SendAsyncScrollDOMEvent(bool aIsRoot, const mozilla::CSSRect &aContentRect,
                                        const mozilla::CSSSize &aScrollableSize) override {}
   virtual void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
                                     APZStateChange aChange,
                                     int aArg) override;
+  virtual void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
+                                         const nsString& aEvent) override;
 private:
   nsCOMPtr<nsIWidget> mWidget;
   nsRefPtr<APZEventState> mAPZEventState;
   MessageLoop* mUILoop;
 
   void InitializeRoot();
   float GetPresShellResolution() const;
   nsIPresShell* GetPresShell() const;
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -499,89 +499,74 @@ TextureClientD3D11::ToSurfaceDescriptor(
     LOGD3D11("Error getting shared handle for texture.");
     return false;
   }
 
   aOutDescriptor = SurfaceDescriptorD3D10((WindowsHandle)sharedHandle, mFormat, mSize);
   return true;
 }
 
-DXGIYCbCrTextureClientD3D11::DXGIYCbCrTextureClientD3D11(ISurfaceAllocator* aAllocator,
-                                                         TextureFlags aFlags)
+DXGIYCbCrTextureClient::DXGIYCbCrTextureClient(ISurfaceAllocator* aAllocator,
+                                               TextureFlags aFlags)
   : TextureClient(aAllocator, aFlags)
   , mIsLocked(false)
 {
-  MOZ_COUNT_CTOR(DXGIYCbCrTextureClientD3D11);
+  MOZ_COUNT_CTOR(DXGIYCbCrTextureClient);
 }
 
 class YCbCrKeepAliveD3D11 : public KeepAlive
 {
 public:
-  YCbCrKeepAliveD3D11(RefPtr<ID3D11Texture2D> aTextures[3])
+  YCbCrKeepAliveD3D11(RefPtr<IUnknown> aTextures[3])
   {
     mTextures[0] = aTextures[0];
     mTextures[1] = aTextures[1];
     mTextures[2] = aTextures[2];
   }
 
 protected:
-  RefPtr<ID3D11Texture2D> mTextures[3];
+  RefPtr<IUnknown> mTextures[3];
 };
 
-DXGIYCbCrTextureClientD3D11::~DXGIYCbCrTextureClientD3D11()
+DXGIYCbCrTextureClient::~DXGIYCbCrTextureClient()
 {
-  if (mTextures[0] && mActor) {
-    KeepUntilFullDeallocation(MakeUnique<YCbCrKeepAliveD3D11>(mTextures));
+  if (mHoldRefs[0] && mActor) {
+    KeepUntilFullDeallocation(MakeUnique<YCbCrKeepAliveD3D11>(mHoldRefs));
   }
-  MOZ_COUNT_DTOR(DXGIYCbCrTextureClientD3D11);
+  MOZ_COUNT_DTOR(DXGIYCbCrTextureClient);
 }
 
 bool
-DXGIYCbCrTextureClientD3D11::Lock(OpenMode)
+DXGIYCbCrTextureClient::Lock(OpenMode)
 {
   MOZ_ASSERT(!mIsLocked);
   if (!IsValid()) {
     return false;
   }
   mIsLocked = true;
   return true;
 }
 
 void
-DXGIYCbCrTextureClientD3D11::Unlock()
+DXGIYCbCrTextureClient::Unlock()
 {
   MOZ_ASSERT(mIsLocked, "Unlock called while the texture is not locked!");
   mIsLocked = false;
 }
 
 bool
-DXGIYCbCrTextureClientD3D11::ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor)
+DXGIYCbCrTextureClient::ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor)
 {
   MOZ_ASSERT(IsValid());
   if (!IsAllocated()) {
     return false;
   }
 
-  RefPtr<IDXGIResource> resource;
-  mTextures[0]->QueryInterface((IDXGIResource**)byRef(resource));
-
-  HANDLE sharedHandleY;
-  HRESULT hr = resource->GetSharedHandle(&sharedHandleY);
-
-  mTextures[1]->QueryInterface((IDXGIResource**)byRef(resource));
-
-  HANDLE sharedHandleCb;
-  hr = resource->GetSharedHandle(&sharedHandleCb);
-
-  mTextures[2]->QueryInterface((IDXGIResource**)byRef(resource));
-
-  HANDLE sharedHandleCr;
-  hr = resource->GetSharedHandle(&sharedHandleCr);
-
-  aOutDescriptor = SurfaceDescriptorDXGIYCbCr((WindowsHandle)sharedHandleY, (WindowsHandle)sharedHandleCb, (WindowsHandle)sharedHandleCr, GetSize());
+  aOutDescriptor = SurfaceDescriptorDXGIYCbCr((WindowsHandle)mHandles[0], (WindowsHandle)mHandles[1], (WindowsHandle)mHandles[2],
+                                              GetSize(), mSizeY, mSizeCbCr);
   return true;
 }
 
 DXGITextureHostD3D11::DXGITextureHostD3D11(TextureFlags aFlags,
                                            const SurfaceDescriptorD3D10& aDescriptor)
   : TextureHost(aFlags)
   , mSize(aDescriptor.size())
   , mHandle(aDescriptor.handle())
--- a/gfx/layers/d3d11/TextureD3D11.h
+++ b/gfx/layers/d3d11/TextureD3D11.h
@@ -73,65 +73,76 @@ protected:
   RefPtr<ID3D11Texture2D> mTexture;
   RefPtr<gfx::DrawTarget> mDrawTarget;
   gfx::SurfaceFormat mFormat;
   bool mIsLocked;
   bool mNeedsClear;
   bool mNeedsClearWhite;
 };
 
-class DXGIYCbCrTextureClientD3D11 : public TextureClient
+class DXGIYCbCrTextureClient : public TextureClient
 {
 public:
-  DXGIYCbCrTextureClientD3D11(ISurfaceAllocator* aAllocator,
-                              TextureFlags aFlags);
+  DXGIYCbCrTextureClient(ISurfaceAllocator* aAllocator,
+                         TextureFlags aFlags);
 
-  virtual ~DXGIYCbCrTextureClientD3D11();
+  virtual ~DXGIYCbCrTextureClient();
 
   // TextureClient
 
-  virtual bool IsAllocated() const override{ return !!mTextures[0]; }
+  virtual bool IsAllocated() const override{ return !!mHoldRefs[0]; }
 
   virtual bool Lock(OpenMode aOpenMode) override;
 
   virtual void Unlock() override;
 
   virtual bool IsLocked() const override{ return mIsLocked; }
 
   virtual bool ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor) override;
 
-  void InitWith(ID3D11Texture2D* aTextureY,
-                ID3D11Texture2D* aTextureCb,
-                ID3D11Texture2D* aTextureCr,
-                const gfx::IntSize& aSize)
+  void InitWith(IUnknown* aTextureY,
+                IUnknown* aTextureCb,
+                IUnknown* aTextureCr,
+                HANDLE aHandleY,
+                HANDLE aHandleCb,
+                HANDLE aHandleCr,
+                const gfx::IntSize& aSize,
+                const gfx::IntSize& aSizeY,
+                const gfx::IntSize& aSizeCbCr)
   {
-    MOZ_ASSERT(aTextureY && aTextureCb && aTextureCr);
-    MOZ_ASSERT(!mTextures[0]);
-    mTextures[0] = aTextureY;
-    mTextures[1] = aTextureCb;
-    mTextures[2] = aTextureCr;
+    mHandles[0] = aHandleY;
+    mHandles[1] = aHandleCb;
+    mHandles[2] = aHandleCr;
+    mHoldRefs[0] = aTextureY;
+    mHoldRefs[1] = aTextureCb;
+    mHoldRefs[2] = aTextureCr;
     mSize = aSize;
+    mSizeY = aSizeY;
+    mSizeCbCr = aSizeCbCr;
   }
 
   virtual gfx::IntSize GetSize() const
   {
     return mSize;
   }
 
   virtual bool HasInternalBuffer() const override{ return true; }
 
     // This TextureClient should not be used in a context where we use CreateSimilar
     // (ex. component alpha) because the underlying texture data is always created by
     // an external producer.
     virtual TemporaryRef<TextureClient>
     CreateSimilar(TextureFlags, TextureAllocationFlags) const override{ return nullptr; }
 
 private:
-  RefPtr<ID3D11Texture2D> mTextures[3];
+  RefPtr<IUnknown> mHoldRefs[3];
+  HANDLE mHandles[3];
   gfx::IntSize mSize;
+  gfx::IntSize mSizeY;
+  gfx::IntSize mSizeCbCr;
   bool mIsLocked;
 };
 
 
 /**
  * TextureSource that provides with the necessary APIs to be composited by a
  * CompositorD3D11.
  */
--- a/gfx/layers/d3d9/TextureD3D9.cpp
+++ b/gfx/layers/d3d9/TextureD3D9.cpp
@@ -61,16 +61,20 @@ CreateTextureHostD3D9(const SurfaceDescr
     case SurfaceDescriptor::TSurfaceDescriptorD3D9: {
       result = new TextureHostD3D9(aFlags, aDesc);
       break;
     }
     case SurfaceDescriptor::TSurfaceDescriptorD3D10: {
       result = new DXGITextureHostD3D9(aFlags, aDesc);
       break;
     }
+    case SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr: {
+      result = new DXGIYCbCrTextureHostD3D9(aFlags, aDesc.get_SurfaceDescriptorDXGIYCbCr());
+      break;
+    }
     default: {
       NS_WARNING("Unsupported SurfaceDescriptor type");
     }
   }
   return result.forget();
 }
 
 static SurfaceFormat
@@ -1027,10 +1031,92 @@ DXGITextureHostD3D9::SetCompositor(Compo
 }
 
 void
 DXGITextureHostD3D9::DeallocateDeviceData()
 {
   mTextureSource = nullptr;
 }
 
+DXGIYCbCrTextureHostD3D9::DXGIYCbCrTextureHostD3D9(TextureFlags aFlags,
+                                                   const SurfaceDescriptorDXGIYCbCr& aDescriptor)
+ : TextureHost(aFlags)
+ , mSize(aDescriptor.size())
+ , mSizeY(aDescriptor.sizeY())
+ , mSizeCbCr(aDescriptor.sizeCbCr())
+ , mIsLocked(false)
+{
+  mHandles[0] = reinterpret_cast<HANDLE>(aDescriptor.handleY());
+  mHandles[1] = reinterpret_cast<HANDLE>(aDescriptor.handleCb());
+  mHandles[2] = reinterpret_cast<HANDLE>(aDescriptor.handleCr());
+}
+
+IDirect3DDevice9*
+DXGIYCbCrTextureHostD3D9::GetDevice()
+{
+  return mCompositor ? mCompositor->device() : nullptr;
+}
+
+void
+DXGIYCbCrTextureHostD3D9::SetCompositor(Compositor* aCompositor)
+{
+  mCompositor = static_cast<CompositorD3D9*>(aCompositor);
+}
+
+bool
+DXGIYCbCrTextureHostD3D9::Lock()
+{
+  if (!GetDevice()) {
+    NS_WARNING("trying to lock a TextureHost without a D3D device");
+    return false;
+  }
+  if (!mTextureSources[0]) {
+    if (!mHandles[0]) {
+      return false;
+    }
+
+    if (FAILED(GetDevice()->CreateTexture(mSizeY.width, mSizeY.height,
+                                          1, 0, D3DFMT_A8, D3DPOOL_DEFAULT,
+                                          byRef(mTextures[0]), &mHandles[0]))) {
+      return false;
+    }
+
+    if (FAILED(GetDevice()->CreateTexture(mSizeCbCr.width, mSizeCbCr.height,
+                                          1, 0, D3DFMT_A8, D3DPOOL_DEFAULT,
+                                          byRef(mTextures[1]), &mHandles[1]))) {
+      return false;
+    }
+
+    if (FAILED(GetDevice()->CreateTexture(mSizeCbCr.width, mSizeCbCr.height,
+                                          1, 0, D3DFMT_A8, D3DPOOL_DEFAULT,
+                                          byRef(mTextures[2]), &mHandles[2]))) {
+      return false;
+    }
+
+    mTextureSources[0] = new DataTextureSourceD3D9(SurfaceFormat::A8, mSize, mCompositor, mTextures[0]);
+    mTextureSources[1] = new DataTextureSourceD3D9(SurfaceFormat::A8, mSize, mCompositor, mTextures[1]);
+    mTextureSources[2] = new DataTextureSourceD3D9(SurfaceFormat::A8, mSize, mCompositor, mTextures[2]);
+    mTextureSources[0]->SetNextSibling(mTextureSources[1]);
+    mTextureSources[1]->SetNextSibling(mTextureSources[2]);
+  }
+
+  mIsLocked = true;
+  return mIsLocked;
+}
+
+void
+DXGIYCbCrTextureHostD3D9::Unlock()
+{
+  MOZ_ASSERT(mIsLocked);
+  mIsLocked = false;
+}
+
+TextureSource*
+DXGIYCbCrTextureHostD3D9::GetTextureSources()
+{
+  MOZ_ASSERT(mIsLocked);
+  // If Lock was successful we must have a valid TextureSource.
+  MOZ_ASSERT(mTextureSources[0] && mTextureSources[1] && mTextureSources[2]);
+  return mTextureSources[0].get();
+}
+
 }
 }
--- a/gfx/layers/d3d9/TextureD3D9.h
+++ b/gfx/layers/d3d9/TextureD3D9.h
@@ -365,16 +365,53 @@ protected:
   RefPtr<DataTextureSourceD3D9> mTextureSource;
   RefPtr<CompositorD3D9> mCompositor;
   WindowsHandle mHandle;
   gfx::SurfaceFormat mFormat;
   gfx::IntSize mSize;
   bool mIsLocked;
 };
 
+class DXGIYCbCrTextureHostD3D9 : public TextureHost
+{
+public:
+  DXGIYCbCrTextureHostD3D9(TextureFlags aFlags,
+                           const SurfaceDescriptorDXGIYCbCr& aDescriptor);
+
+  virtual TextureSource* GetTextureSources() override;
+
+  virtual void DeallocateDeviceData() override {}
+
+  virtual void SetCompositor(Compositor* aCompositor) override;
+
+  virtual gfx::SurfaceFormat GetFormat() const override { return gfx::SurfaceFormat::YUV; }
+
+  virtual bool Lock() override;
+  virtual void Unlock() override;
+  virtual gfx::IntSize GetSize() const override { return mSize; }
+
+  virtual TemporaryRef<gfx::DataSourceSurface> GetAsSurface() override
+  {
+    return nullptr;
+  }
+
+ protected:
+  IDirect3DDevice9* GetDevice();
+
+  HANDLE mHandles[3];
+  RefPtr<IDirect3DTexture9> mTextures[3];
+  RefPtr<DataTextureSourceD3D9> mTextureSources[3];
+
+  RefPtr<CompositorD3D9> mCompositor;
+  gfx::IntSize mSize;
+  gfx::IntSize mSizeY;
+  gfx::IntSize mSizeCbCr;
+  bool mIsLocked;
+ };
+
 class CompositingRenderTargetD3D9 : public CompositingRenderTarget,
                                     public TextureSourceD3D9
 {
 public:
   CompositingRenderTargetD3D9(IDirect3DTexture9* aTexture,
                               SurfaceInitMode aInit,
                               const gfx::IntRect& aRect);
   // use for rendering to the main window, cannot be rendered as a texture
--- a/gfx/layers/ipc/LayersSurfaces.ipdlh
+++ b/gfx/layers/ipc/LayersSurfaces.ipdlh
@@ -50,16 +50,18 @@ struct SurfaceDescriptorD3D10 {
   IntSize size;
 };
 
 struct SurfaceDescriptorDXGIYCbCr {
   WindowsHandle handleY;
   WindowsHandle handleCb;
   WindowsHandle handleCr;
   IntSize size;
+  IntSize sizeY;
+  IntSize sizeCbCr;
 };
 
 struct SurfaceDescriptorMacIOSurface {
   uint32_t surface;
   double scaleFactor;
   bool isOpaque;
 };
 
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -332,16 +332,19 @@ private:
   DECL_GFX_PREF(Once, "layout.paint_rects_separately",         LayoutPaintRectsSeparately, bool, true);
 
   // This and code dependent on it should be removed once containerless scrolling looks stable.
   DECL_GFX_PREF(Once, "layout.scroll.root-frame-containers",   LayoutUseContainersForRootFrames, bool, true);
 
   // This affects whether events will be routed through APZ or not.
   DECL_GFX_PREF(Once, "mousewheel.system_scroll_override_on_root_content.enabled",
                                                                MouseWheelHasScrollDeltaOverride, bool, false);
+  DECL_GFX_PREF(Live, "mousewheel.transaction.ignoremovedelay",MouseWheelIgnoreMoveDelayMs, int32_t, (int32_t)100);
+  DECL_GFX_PREF(Live, "mousewheel.transaction.timeout",        MouseWheelTransactionTimeoutMs, int32_t, (int32_t)1500);
+  DECL_GFX_PREF(Live, "test.mousescroll",                      MouseScrollTestingEnabled, bool, false);
 
   DECL_GFX_PREF(Live, "nglayout.debug.widget_update_flashing", WidgetUpdateFlashing, bool, false);
   DECL_GFX_PREF(Live, "ui.click_hold_context_menus.delay",     UiClickHoldContextMenusDelay, int32_t, 500);
   DECL_GFX_PREF(Once, "webgl.angle.force-d3d11",               WebGLANGLEForceD3D11, bool, false);
   DECL_GFX_PREF(Once, "webgl.angle.try-d3d11",                 WebGLANGLETryD3D11, bool, false);
   DECL_GFX_PREF(Once, "webgl.force-layers-readback",           WebGLForceLayersReadback, bool, false);
 
   // WARNING:
--- a/layout/base/UnitTransforms.h
+++ b/layout/base/UnitTransforms.h
@@ -25,31 +25,40 @@ enum class PixelCastJustification : uint
   // For the root composition size we want to view it as layer pixels in any layer
   ParentLayerToLayerForRootComposition,
   // The Layer coordinate space for one layer is the ParentLayer coordinate
   // space for its children
   MovingDownToChildren,
   // The transform that is usually used to convert between two coordinate
   // systems is not available (for example, because the object that stores it
   // is being destroyed), so fall back to the identity.
-  TransformNotAvailable
+  TransformNotAvailable,
+  // When an OS event is initially constructed, its reference point is
+  // technically in screen pixels, as it has not yet accounted for any
+  // asynchronous transforms. This justification is for viewing the initial
+  // reference point as a screen point.
+  LayoutDeviceToScreenForUntransformedEvent
 };
 
 template <class TargetUnits, class SourceUnits>
 gfx::SizeTyped<TargetUnits> ViewAs(const gfx::SizeTyped<SourceUnits>& aSize, PixelCastJustification) {
   return gfx::SizeTyped<TargetUnits>(aSize.width, aSize.height);
 }
 template <class TargetUnits, class SourceUnits>
 gfx::IntSizeTyped<TargetUnits> ViewAs(const gfx::IntSizeTyped<SourceUnits>& aSize, PixelCastJustification) {
   return gfx::IntSizeTyped<TargetUnits>(aSize.width, aSize.height);
 }
 template <class TargetUnits, class SourceUnits>
 gfx::PointTyped<TargetUnits> ViewAs(const gfx::PointTyped<SourceUnits>& aPoint, PixelCastJustification) {
   return gfx::PointTyped<TargetUnits>(aPoint.x, aPoint.y);
 }
+template <class TargetUnits, class SourceUnits>
+gfx::IntPointTyped<TargetUnits> ViewAs(const gfx::IntPointTyped<SourceUnits>& aPoint, PixelCastJustification) {
+  return gfx::IntPointTyped<TargetUnits>(aPoint.x, aPoint.y);
+}
 template <class NewTargetUnits, class OldTargetUnits, class SourceUnits>
 gfx::ScaleFactor<SourceUnits, NewTargetUnits> ViewTargetAs(
     const gfx::ScaleFactor<SourceUnits, OldTargetUnits>& aScaleFactor,
     PixelCastJustification) {
   return gfx::ScaleFactor<SourceUnits, NewTargetUnits>(aScaleFactor.scale);
 }
 
 // Convenience functions for casting untyped entities to typed entities.
--- a/layout/generic/WritingModes.h
+++ b/layout/generic/WritingModes.h
@@ -1467,16 +1467,40 @@ public:
   }
   nscoord& BSize(WritingMode aWritingMode) // block-size
   {
     CHECK_WRITING_MODE(aWritingMode);
     return mRect.height;
   }
 
   /**
+   * Accessors for line-relative coordinates
+   */
+  nscoord LineLeft(WritingMode aWritingMode, nscoord aContainerWidth) const
+  {
+    CHECK_WRITING_MODE(aWritingMode);
+    if (aWritingMode.IsVertical()) {
+      return IStart(); // sideways-left will require aContainerHeight
+    } else {
+      return aWritingMode.IsBidiLTR() ? IStart()
+                                      : aContainerWidth - IEnd();
+    }
+  }
+  nscoord LineRight(WritingMode aWritingMode, nscoord aContainerWidth) const
+  {
+    CHECK_WRITING_MODE(aWritingMode);
+    if (aWritingMode.IsVertical()) {
+      return IEnd(); // sideways-left will require aContainerHeight
+    } else {
+      return aWritingMode.IsBidiLTR() ? IEnd()
+                                      : aContainerWidth - IStart();
+    }
+  }
+
+  /**
    * Physical coordinates of the rect.
    */
   nscoord X(WritingMode aWritingMode, nscoord aContainerWidth) const
   {
     CHECK_WRITING_MODE(aWritingMode);
     if (aWritingMode.IsVertical()) {
       return aWritingMode.IsVerticalLR() ?
              mRect.Y() : aContainerWidth - mRect.YMost();
@@ -1598,29 +1622,16 @@ public:
   void SetEmpty() { mRect.SetEmpty(); }
 
   bool IsEqualEdges(const LogicalRect aOther) const
   {
     CHECK_WRITING_MODE(aOther.GetWritingMode());
     return mRect.IsEqualEdges(aOther.mRect);
   }
 
-/* XXX are these correct?
-  nscoord ILeft(WritingMode aWritingMode) const
-  {
-    CHECK_WRITING_MODE(aWritingMode);
-    return aWritingMode.IsBidiLTR() ? IStart() : IEnd();
-  }
-  nscoord IRight(WritingMode aWritingMode) const
-  {
-    CHECK_WRITING_MODE(aWritingMode);
-    return aWritingMode.IsBidiLTR() ? IEnd() : IStart();
-  }
-*/
-
   LogicalPoint Origin(WritingMode aWritingMode) const
   {
     CHECK_WRITING_MODE(aWritingMode);
     return LogicalPoint(aWritingMode, IStart(), BStart());
   }
   void SetOrigin(WritingMode aWritingMode, const LogicalPoint& aPoint)
   {
     IStart(aWritingMode) = aPoint.I(aWritingMode);
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -1935,25 +1935,20 @@ nsBlockFrame::PropagateFloatDamage(nsBlo
     WritingMode wm = aState.mReflowState.GetWritingMode();
     nscoord containerWidth = aState.ContainerWidth();
     LogicalRect overflow = aLine->GetOverflowArea(eScrollableOverflow, wm,
                                                   containerWidth);
     nscoord lineBCoordCombinedBefore = overflow.BStart(wm) + aDeltaBCoord;
     nscoord lineBCoordCombinedAfter = lineBCoordCombinedBefore +
                                       overflow.BSize(wm);
 
-    // "Translate" the float manager with an offset of (0, 0) in order to
-    // set the origin to our writing mode
-    LogicalPoint oPt(wm);
-    WritingMode oldWM = floatManager->Translate(wm, oPt);
     bool isDirty = floatManager->IntersectsDamage(wm, lineBCoordBefore,
                                                   lineBCoordAfter) ||
                    floatManager->IntersectsDamage(wm, lineBCoordCombinedBefore,
                                                   lineBCoordCombinedAfter);
-    floatManager->Untranslate(oldWM, oPt);
     if (isDirty) {
       aLine->MarkDirty();
       return;
     }
   }
 
   // Check if the line is moving relative to the float manager
   if (aDeltaBCoord + aState.mReflowState.mBlockDelta != 0) {
@@ -3341,18 +3336,17 @@ nsBlockFrame::ReflowBlockFrame(nsBlockRe
     // Reflow the block into the available space
     // construct the html reflow state for the block. ReflowBlock
     // will initialize it
     nsHTMLReflowState
       blockHtmlRS(aState.mPresContext, aState.mReflowState, frame,
                   availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm));
     blockHtmlRS.mFlags.mHasClearance = aLine->HasClearance();
 
-    nsFloatManager::SavedState
-      floatManagerState(aState.mReflowState.GetWritingMode());
+    nsFloatManager::SavedState floatManagerState;
     if (mayNeedRetry) {
       blockHtmlRS.mDiscoveredClearance = &clearanceFrame;
       aState.mFloatManager->PushState(&floatManagerState);
     } else if (!applyBStartMargin) {
       blockHtmlRS.mDiscoveredClearance = aState.mReflowState.mDiscoveredClearance;
     }
 
     nsReflowStatus frameReflowStatus = NS_FRAME_COMPLETE;
@@ -3607,18 +3601,17 @@ nsBlockFrame::ReflowInlineFrames(nsBlock
   do {
     nscoord availableSpaceHeight = 0;
     do {
       bool allowPullUp = true;
       nsIFrame* forceBreakInFrame = nullptr;
       int32_t forceBreakOffset = -1;
       gfxBreakPriority forceBreakPriority = gfxBreakPriority::eNoBreak;
       do {
-        nsFloatManager::SavedState
-          floatManagerState(aState.mReflowState.GetWritingMode());
+        nsFloatManager::SavedState floatManagerState;
         aState.mReflowState.mFloatManager->PushState(&floatManagerState);
 
         // Once upon a time we allocated the first 30 nsLineLayout objects
         // on the stack, and then we switched to the heap.  At that time
         // these objects were large (1100 bytes on a 32 bit system).
         // Then the nsLineLayout object was shrunk to 156 bytes by
         // removing some internal buffers.  Given that it is so much
         // smaller, the complexity of 2 different ways of allocating
@@ -6205,20 +6198,23 @@ nsBlockFrame::RecoverFloatsFor(nsIFrame*
   nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aFrame);
   // Don't recover any state inside a block that has its own space manager
   // (we don't currently have any blocks like this, though, thanks to our
   // use of extra frames for 'overflow')
   if (block && !nsBlockFrame::BlockNeedsFloatManager(block)) {
     // If the element is relatively positioned, then adjust x and y
     // accordingly so that we consider relatively positioned frames
     // at their original position.
-    LogicalPoint pos = block->GetLogicalNormalPosition(aWM, aContainerWidth);
-    WritingMode oldWM = aFloatManager.Translate(aWM, pos);
+
+    LogicalRect rect(aWM, block->GetNormalRect(), aContainerWidth);
+    nscoord lineLeft = rect.LineLeft(aWM, aContainerWidth);
+    nscoord blockStart = rect.BStart(aWM);
+    aFloatManager.Translate(lineLeft, blockStart);
     block->RecoverFloats(aFloatManager, aWM, aContainerWidth);
-    aFloatManager.Untranslate(oldWM, pos);
+    aFloatManager.Translate(-lineLeft, -blockStart);
   }
 }
 
 //////////////////////////////////////////////////////////////////////
 // Painting, event handling
 
 #ifdef DEBUG
 static void ComputeVisualOverflowArea(nsLineList& aLines,
--- a/layout/generic/nsBlockReflowContext.cpp
+++ b/layout/generic/nsBlockReflowContext.cpp
@@ -247,52 +247,57 @@ nsBlockReflowContext::ReflowBlock(const 
 
     // Adjust the available block size if it's constrained so that the
     // child frame doesn't think it can reflow into its margin area.
     if (NS_UNCONSTRAINEDSIZE != aFrameRS.AvailableBSize()) {
       aFrameRS.AvailableBSize() -= mBStartMargin.get() + aClearance;
     }
   }
 
-  LogicalPoint tPt(mWritingMode);
+  nscoord tI = 0, tB = 0;
   // The values of x and y do not matter for floats, so don't bother
   // calculating them. Floats are guaranteed to have their own float
   // manager, so tI and tB don't matter.  mICoord and mBCoord don't
   // matter becacuse they are only used in PlaceBlock, which is not used
   // for floats.
   if (aLine) {
     // Compute inline/block coordinate where reflow will begin. Use the
     // rules from 10.3.3 to determine what to apply. At this point in the
     // reflow auto inline-start/end margins will have a zero value.
 
     WritingMode frameWM = aFrameRS.GetWritingMode();
-    mICoord = tPt.I(mWritingMode) =
-      mSpace.IStart(mWritingMode) +
-      aFrameRS.ComputedLogicalMargin().ConvertTo(mWritingMode,
-                                                 frameWM).IStart(mWritingMode);
-    mBCoord = tPt.B(mWritingMode) = mSpace.BStart(mWritingMode) +
-                                    mBStartMargin.get() + aClearance;
+    LogicalMargin usedMargin =
+      aFrameRS.ComputedLogicalMargin().ConvertTo(mWritingMode, frameWM);
+    mICoord = mSpace.IStart(mWritingMode) + usedMargin.IStart(mWritingMode);
+    mBCoord = mSpace.BStart(mWritingMode) + mBStartMargin.get() + aClearance;
+
+    LogicalRect space(mWritingMode, mICoord, mBCoord,
+                      mSpace.ISize(mWritingMode) -
+                      usedMargin.IStartEnd(mWritingMode),
+                      mSpace.BSize(mWritingMode) -
+                      usedMargin.BStartEnd(mWritingMode));
+    tI = space.LineLeft(mWritingMode, mContainerWidth);
+    tB = mBCoord;
 
     if ((mFrame->GetStateBits() & NS_BLOCK_FLOAT_MGR) == 0)
       aFrameRS.mBlockDelta =
         mOuterReflowState.mBlockDelta + mBCoord - aLine->BStart();
   }
 
   // Let frame know that we are reflowing it
   mFrame->WillReflow(mPresContext);
 
 #ifdef DEBUG
   mMetrics.ISize(mWritingMode) = nscoord(0xdeadbeef);
   mMetrics.BSize(mWritingMode) = nscoord(0xdeadbeef);
 #endif
 
-  WritingMode oldWM = mOuterReflowState.mFloatManager->Translate(mWritingMode,
-                                                                 tPt);
+  mOuterReflowState.mFloatManager->Translate(tI, tB);
   mFrame->Reflow(mPresContext, mMetrics, aFrameRS, aFrameReflowStatus);
-  mOuterReflowState.mFloatManager->Untranslate(oldWM, tPt);
+  mOuterReflowState.mFloatManager->Translate(-tI, -tB);
 
 #ifdef DEBUG
   if (!NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus)) {
     if (CRAZY_SIZE(mMetrics.ISize(mWritingMode)) ||
         CRAZY_SIZE(mMetrics.BSize(mWritingMode))) {
       printf("nsBlockReflowContext: ");
       nsFrame::ListTag(stdout, mFrame);
       printf(" metrics=%d,%d!\n",
--- a/layout/generic/nsBlockReflowState.cpp
+++ b/layout/generic/nsBlockReflowState.cpp
@@ -33,18 +33,16 @@ nsBlockReflowState::nsBlockReflowState(c
                                        nsBlockFrame* aFrame,
                                        bool aBStartMarginRoot,
                                        bool aBEndMarginRoot,
                                        bool aBlockNeedsFloatManager,
                                        nscoord aConsumedBSize)
   : mBlock(aFrame),
     mPresContext(aPresContext),
     mReflowState(aReflowState),
-    mFloatManagerOrigin(aReflowState.GetWritingMode()),
-    mFloatManagerStateBefore(aReflowState.GetWritingMode()),
     mContentArea(aReflowState.GetWritingMode()),
     mPushedFloats(nullptr),
     mOverflowTracker(nullptr),
     mBorderPadding(mReflowState.ComputedLogicalBorderPadding()),
     mPrevBEndMargin(),
     mLineNumber(0),
     mFlags(0),
     mFloatBreakType(NS_STYLE_CLEAR_NONE),
@@ -101,17 +99,17 @@ nsBlockReflowState::nsBlockReflowState(c
   }
   
   mFloatManager = aReflowState.mFloatManager;
 
   NS_ASSERTION(mFloatManager,
                "FloatManager should be set in nsBlockReflowState" );
   if (mFloatManager) {
     // Save the coordinate system origin for later.
-    mFloatManager->GetTranslation(mFloatManagerWM, mFloatManagerOrigin);
+    mFloatManager->GetTranslation(mFloatManagerI, mFloatManagerB);
     mFloatManager->PushState(&mFloatManagerStateBefore); // never popped
   }
 
   mReflowStatus = NS_FRAME_COMPLETE;
 
   mNextInFlow = static_cast<nsBlockFrame*>(mBlock->GetNextInFlow());
 
   NS_WARN_IF_FALSE(NS_UNCONSTRAINEDSIZE != aReflowState.ComputedISize(),
@@ -306,26 +304,21 @@ nsBlockReflowState::ComputeBlockAvailSpa
 nsFlowAreaRect
 nsBlockReflowState::GetFloatAvailableSpaceWithState(
                       nscoord aBCoord,
                       nsFloatManager::SavedState *aState) const
 {
   WritingMode wm = mReflowState.GetWritingMode();
 #ifdef DEBUG
   // Verify that the caller setup the coordinate system properly
-  WritingMode wWM;
-  LogicalPoint wPt(wWM);
-  mFloatManager->GetTranslation(wWM, wPt);
+  nscoord wI, wB;
+  mFloatManager->GetTranslation(wI, wB);
 
-  if (wWM == mFloatManagerWM) {
-    NS_ASSERTION(wPt == mFloatManagerOrigin, "bad coord system");
-  } else {
-    //XXX if the writing modes are different we can't easily assert that
-    //    the origin is the same.
-  }
+  NS_ASSERTION((wI == mFloatManagerI) && (wB == mFloatManagerB),
+               "bad coord system");
 #endif
 
   nscoord blockSize = (mContentArea.BSize(wm) == nscoord_MAX)
     ? nscoord_MAX : std::max(mContentArea.BEnd(wm) - aBCoord, 0);
   nsFlowAreaRect result =
     mFloatManager->GetFlowArea(wm, aBCoord, nsFloatManager::BAND_FROM_POINT,
                                blockSize, mContentArea, aState,
                                ContainerWidth());
@@ -348,25 +341,21 @@ nsBlockReflowState::GetFloatAvailableSpa
 nsFlowAreaRect
 nsBlockReflowState::GetFloatAvailableSpaceForBSize(
                       nscoord aBCoord, nscoord aBSize,
                       nsFloatManager::SavedState *aState) const
 {
   WritingMode wm = mReflowState.GetWritingMode();
 #ifdef DEBUG
   // Verify that the caller setup the coordinate system properly
-  WritingMode wWM;
-  LogicalPoint wPt(wWM);
-  mFloatManager->GetTranslation(wWM, wPt);
-  if (wWM == mFloatManagerWM) {
-    NS_ASSERTION(wPt == mFloatManagerOrigin, "bad coord system");
-  } else {
-    //XXX if the writing modes are different we can't easily assert that
-    //    the origin is the same.
-  }
+  nscoord wI, wB;
+  mFloatManager->GetTranslation(wI, wB);
+
+  NS_ASSERTION((wI == mFloatManagerI) && (wB == mFloatManagerB),
+               "bad coord system");
 #endif
   nsFlowAreaRect result =
     mFloatManager->GetFlowArea(wm, aBCoord, nsFloatManager::WIDTH_WITHIN_HEIGHT,
                                aBSize, mContentArea, aState, ContainerWidth());
   // Keep the width >= 0 for compatibility with nsSpaceManager.
   if (result.mRect.ISize(wm) < 0) {
     result.mRect.ISize(wm) = 0;
   }
@@ -464,41 +453,34 @@ nsBlockReflowState::AppendPushedFloatCha
  * incremental reflow O(N^2) and this state should really be kept
  * around, attached to the frame tree.
  */
 void
 nsBlockReflowState::RecoverFloats(nsLineList::iterator aLine,
                                   nscoord aDeltaBCoord)
 {
   WritingMode wm = mReflowState.GetWritingMode();
-  // "Translate" the float manager with an offset of (0, 0) in order to
-  // set the origin to our writing mode
-  LogicalPoint oPt(wm);
-  WritingMode oldWM = mFloatManager->Translate(wm, oPt);
   if (aLine->HasFloats()) {
     // Place the floats into the space-manager again. Also slide
     // them, just like the regular frames on the line.
     nsFloatCache* fc = aLine->GetFirstFloat();
     while (fc) {
       nsIFrame* floatFrame = fc->mFloat;
       if (aDeltaBCoord != 0) {
         floatFrame->MovePositionBy(nsPoint(0, aDeltaBCoord));
         nsContainerFrame::PositionFrameView(floatFrame);
         nsContainerFrame::PositionChildViews(floatFrame);
       }
 #ifdef DEBUG
       if (nsBlockFrame::gNoisyReflow || nsBlockFrame::gNoisyFloatManager) {
-        WritingMode tWM;
-        LogicalPoint tPt(tWM);
-        mFloatManager->GetTranslation(tWM, tPt);
+        nscoord tI, tB;
+        mFloatManager->GetTranslation(tI, tB);
         nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
-        printf("RecoverFloats: txy=%d,%d (%d,%d) ",
-               tPt.I(tWM), tPt.B(tWM),
-               mFloatManagerOrigin.I(mFloatManagerWM),
-               mFloatManagerOrigin.B(mFloatManagerWM));
+        printf("RecoverFloats: tIB=%d,%d (%d,%d) ",
+               tI, tB, mFloatManagerI, mFloatManagerB);
         nsFrame::ListTag(stdout, floatFrame);
         LogicalRect region = nsFloatManager::GetRegionFor(wm, floatFrame,
                                                           ContainerWidth());
         printf(" aDeltaBCoord=%d region={%d,%d,%d,%d}\n",
                aDeltaBCoord, region.IStart(wm), region.BStart(wm),
                region.ISize(wm), region.BSize(wm));
       }
 #endif
@@ -507,17 +489,16 @@ nsBlockReflowState::RecoverFloats(nsLine
                                                            ContainerWidth()),
                               wm, ContainerWidth());
       fc = fc->Next();
     }
   } else if (aLine->IsBlock()) {
     nsBlockFrame::RecoverFloatsFor(aLine->mFirstChild, *mFloatManager, wm,
                                    ContainerWidth());
   }
-  mFloatManager->Untranslate(oldWM, oPt);
 }
 
 /**
  * Everything done in this function is done O(N) times for each pass of
  * reflow so it is O(N*M) where M is the number of incremental reflow
  * passes.  That's bad.  Don't do stuff here.
  *
  * When this function is called, |aLine| has just been slid by |aDeltaBCoord|
@@ -593,20 +574,21 @@ nsBlockReflowState::AddFloat(nsLineLayou
     mBlock->mFloats.AppendFrame(mBlock, aFloat);
   }
 
   // Because we are in the middle of reflowing a placeholder frame
   // within a line (and possibly nested in an inline frame or two
   // that's a child of our block) we need to restore the space
   // manager's translation to the space that the block resides in
   // before placing the float.
-  WritingMode oldWM;
-  LogicalPoint oPt(oldWM);
-  mFloatManager->GetTranslation(oldWM, oPt);
-  mFloatManager->SetTranslation(mFloatManagerWM, mFloatManagerOrigin);
+  nscoord oI, oB;
+  mFloatManager->GetTranslation(oI, oB);
+  nscoord dI = oI - mFloatManagerI;
+  nscoord dB = oB - mFloatManagerB;
+  mFloatManager->Translate(-dI, -dB);
 
   bool placed;
 
   // Now place the float immediately if possible. Otherwise stash it
   // away in mPendingFloats and place it later.
   // If one or more floats has already been pushed to the next line,
   // don't let this one go on the current line, since that would violate
   // float ordering.
@@ -636,17 +618,17 @@ nsBlockReflowState::AddFloat(nsLineLayou
     // deal with this in PlaceBelowCurrentLineFloats
     placed = true;
     // This float will be placed after the line is done (it is a
     // below-current-line float).
     mBelowCurrentLineFloats.Append(mFloatCacheFreeList.Alloc(aFloat));
   }
 
   // Restore coordinate system
-  mFloatManager->SetTranslation(oldWM, oPt);
+  mFloatManager->Translate(dI, dB);
 
   return placed;
 }
 
 bool
 nsBlockReflowState::CanPlaceFloat(nscoord aFloatISize,
                                   const nsFlowAreaRect& aFloatAvailableSpace)
 {
@@ -703,22 +685,17 @@ nsBlockReflowState::FlowAndPlaceFloat(ns
   const nsStyleDisplay* floatDisplay = aFloat->StyleDisplay();
 
   // The float's old region, so we can propagate damage.
   LogicalRect oldRegion = nsFloatManager::GetRegionFor(wm, aFloat,
                                                        ContainerWidth());
 
   // Enforce CSS2 9.5.1 rule [2], i.e., make sure that a float isn't
   // ``above'' another float that preceded it in the flow.
-  // "Translate" the float manager with an offset of (0, 0) in order to
-  // set the origin to our writing mode
-  LogicalPoint oPt(wm);
-  WritingMode oldWM = mFloatManager->Translate(wm, oPt);
-  mBCoord = std::max(mFloatManager->GetLowestFloatTop(wm, ContainerWidth()),
-                     mBCoord);
+  mBCoord = std::max(mFloatManager->GetLowestFloatTop(), mBCoord);
 
   // See if the float should clear any preceding floats...
   // XXX We need to mark this float somehow so that it gets reflowed
   // when floats are inserted before it.
   if (NS_STYLE_CLEAR_NONE != floatDisplay->mBreakType) {
     // XXXldb Does this handle vertical margins correctly?
     mBCoord = ClearFloats(mBCoord, floatDisplay->mBreakType);
   }
@@ -844,17 +821,19 @@ nsBlockReflowState::FlowAndPlaceFloat(ns
     mustPlaceFloat = false;
   }
 
   // If the float is continued, it will get the same absolute x value as its prev-in-flow
 
   // We don't worry about the geometry of the prev in flow, let the continuation
   // place and size itself as required.
 
-  // Assign inline and block dir coordinates to the float.
+  // Assign inline and block dir coordinates to the float. We don't use
+  // LineLeft() and LineRight() here, because we would only have to
+  // convert the result back into this block's writing mode.
   LogicalPoint floatPos(wm);
   if ((NS_STYLE_FLOAT_LEFT == floatDisplay->mFloats) == wm.IsBidiLTR()) {
     floatPos.I(wm) = floatAvailableSpace.mRect.IStart(wm);
   }
   else {
     if (!keepFloatOnSameLine) {
       floatPos.I(wm) = floatAvailableSpace.mRect.IEnd(wm) - floatMarginISize;
     }
@@ -974,40 +953,35 @@ nsBlockReflowState::FlowAndPlaceFloat(ns
 
   if (!NS_FRAME_IS_FULLY_COMPLETE(reflowStatus)) {
     mBlock->SplitFloat(*this, aFloat, reflowStatus);
   } else {
     MOZ_ASSERT(!aFloat->GetNextInFlow());
   }
 
 #ifdef NOISY_FLOATMANAGER
-  WritingMode tWM;
-  LogicalPoint tPt(wm);
-  mFloatManager->GetTranslation(tWM, tPt);
-  nsFrame::ListTag(stdout, mBlock);
-  printf(": FlowAndPlaceFloat: AddFloat: txy=%d,%d (%d,%d) {%d,%d,%d,%d}\n",
-         tPt.I(tWM), tPt.B(tWM),
-         mFloatManagerOrigin.I(mFloatManagerWM),
-         mFloatManagerOrigin.B(mFloatManagerWM),
+  nscoord tI, tB;
+  mFloatManager->GetTranslation(tI, tB);
+  nsIFrame::ListTag(stdout, mBlock);
+  printf(": FlowAndPlaceFloat: AddFloat: tIB=%d,%d (%d,%d) {%d,%d,%d,%d}\n",
+         tI, tB, mFloatManagerI, mFloatManagerB,
          region.IStart(wm), region.BStart(wm),
          region.ISize(wm), region.BSize(wm));
 #endif
 
 #ifdef DEBUG
   if (nsBlockFrame::gNoisyReflow) {
     nsRect r = aFloat->GetRect();
     nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
     printf("placed float: ");
     nsFrame::ListTag(stdout, aFloat);
     printf(" %d,%d,%d,%d\n", r.x, r.y, r.width, r.height);
   }
 #endif
 
-  mFloatManager->Untranslate(oldWM, oPt);
-
   return true;
 }
 
 void
 nsBlockReflowState::PushFloatPastBreak(nsIFrame *aFloat)
 {
   // This ensures that we:
   //  * don't try to place later but smaller floats (which CSS says
@@ -1080,18 +1054,17 @@ nsBlockReflowState::ClearFloats(nscoord 
   if (!mFloatManager->HasAnyFloats()) {
     return aBCoord;
   }
 
   nscoord newBCoord = aBCoord;
   WritingMode wm = mReflowState.GetWritingMode();
 
   if (aBreakType != NS_STYLE_CLEAR_NONE) {
-    newBCoord = mFloatManager->ClearFloats(wm, newBCoord, aBreakType,
-                                           ContainerWidth(), aFlags);
+    newBCoord = mFloatManager->ClearFloats(newBCoord, aBreakType, aFlags);
   }
 
   if (aReplacedBlock) {
     for (;;) {
       nsFlowAreaRect floatAvailableSpace = GetFloatAvailableSpace(newBCoord);
       if (!floatAvailableSpace.mHasFloats) {
         // If there aren't any floats here, then we always fit.
         // We check this before calling ISizeToClearPastFloats, which is
--- a/layout/generic/nsBlockReflowState.h
+++ b/layout/generic/nsBlockReflowState.h
@@ -154,18 +154,17 @@ public:
 
   nsFloatManager* mFloatManager;
 
   // The coordinates within the float manager where the block is being
   // placed <b>after</b> taking into account the blocks border and
   // padding. This, therefore, represents the inner "content area" (in
   // spacemanager coordinates) where child frames will be placed,
   // including child blocks and floats.
-  mozilla::WritingMode mFloatManagerWM;
-  mozilla::LogicalPoint mFloatManagerOrigin;
+  nscoord mFloatManagerI, mFloatManagerB;
 
   // XXX get rid of this
   nsReflowStatus mReflowStatus;
 
   // The float manager state as it was before the contents of this
   // block.  This is needed for positioning bullets, since we only want
   // to move the bullet to flow around floats that were before this
   // block, not floats inside of it.
--- a/layout/generic/nsFloatManager.cpp
+++ b/layout/generic/nsFloatManager.cpp
@@ -35,17 +35,17 @@ PSArenaFreeCB(size_t aSize, void* aPtr, 
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // nsFloatManager
 
 nsFloatManager::nsFloatManager(nsIPresShell* aPresShell,
                                mozilla::WritingMode aWM)
   : mWritingMode(aWM),
-    mOffset(aWM),
+    mLineLeft(0), mBlockStart(0),
     mFloatDamage(PSArenaAllocCB, PSArenaFreeCB, aPresShell),
     mPushedLeftFloatPastBreak(false),
     mPushedRightFloatPastBreak(false),
     mSplitLeftFloatAcrossBreak(false),
     mSplitRightFloatAcrossBreak(false)
 {
   MOZ_COUNT_CTOR(nsFloatManager);
 }
@@ -116,18 +116,17 @@ nsFloatManager::GetFlowArea(WritingMode 
                             BandInfoType aInfoType, nscoord aBSize,
                             LogicalRect aContentArea, SavedState* aState,
                             nscoord aContainerWidth) const
 {
   NS_ASSERTION(aBSize >= 0, "unexpected max block size");
   NS_ASSERTION(aContentArea.ISize(aWM) >= 0,
                "unexpected content area inline size");
 
-  LogicalPoint offset = mOffset.ConvertTo(aWM, mWritingMode, 0);
-  nscoord blockStart = aBOffset + offset.B(aWM);
+  nscoord blockStart = aBOffset + mBlockStart;
   if (blockStart < nscoord_MIN) {
     NS_WARNING("bad value");
     blockStart = nscoord_MIN;
   }
 
   // Determine the last float that we should consider.
   uint32_t floatCount;
   if (aState) {
@@ -157,43 +156,41 @@ nsFloatManager::GetFlowArea(WritingMode 
     blockEnd = nscoord_MAX;
   } else {
     blockEnd = blockStart + aBSize;
     if (blockEnd < blockStart || blockEnd > nscoord_MAX) {
       NS_WARNING("bad value");
       blockEnd = nscoord_MAX;
     }
   }
-  nscoord inlineStart = offset.I(aWM) + aContentArea.IStart(aWM);
-  nscoord inlineEnd = offset.I(aWM) + aContentArea.IEnd(aWM);
-  if (inlineEnd < inlineStart) {
+  nscoord lineLeft = mLineLeft + aContentArea.LineLeft(aWM, aContainerWidth);
+  nscoord lineRight = mLineLeft + aContentArea.LineRight(aWM, aContainerWidth);
+  if (lineRight < lineLeft) {
     NS_WARNING("bad value");
-    inlineEnd = inlineStart;
+    lineRight = lineLeft;
   }
 
   // Walk backwards through the floats until we either hit the front of
   // the list or we're above |blockStart|.
   bool haveFloats = false;
   for (uint32_t i = floatCount; i > 0; --i) {
     const FloatInfo &fi = mFloats[i-1];
     if (fi.mLeftBEnd <= blockStart && fi.mRightBEnd <= blockStart) {
       // There aren't any more floats that could intersect this band.
       break;
     }
-    if (fi.mRect.IsEmpty()) {
+    if (fi.IsEmpty()) {
       // For compatibility, ignore floats with empty rects, even though it
       // disagrees with the spec.  (We might want to fix this in the
       // future, though.)
       continue;
     }
 
-    LogicalRect rect = fi.mRect.ConvertTo(aWM, fi.mWritingMode,
-                                          aContainerWidth);
-    nscoord floatBStart = rect.BStart(aWM);
-    nscoord floatBEnd = rect.BEnd(aWM);
+    nscoord floatBStart = fi.BStart();
+    nscoord floatBEnd = fi.BEnd();
     if (blockStart < floatBStart && aInfoType == BAND_FROM_POINT) {
       // This float is below our band.  Shrink our band's height if needed.
       if (floatBStart < blockEnd) {
         blockEnd = floatBStart;
       }
     }
     // If blockStart == blockEnd (which happens only with WIDTH_WITHIN_HEIGHT),
     // we include floats that begin at our 0-height vertical area.  We
@@ -206,81 +203,88 @@ nsFloatManager::GetFlowArea(WritingMode 
       // This float is in our band.
 
       // Shrink our band's height if needed.
       if (floatBEnd < blockEnd && aInfoType == BAND_FROM_POINT) {
         blockEnd = floatBEnd;
       }
 
       // Shrink our band's width if needed.
-      if ((fi.mFrame->StyleDisplay()->mFloats == NS_STYLE_FLOAT_LEFT) ==
-          aWM.IsBidiLTR()) {
-        // A left float in an ltr block or a right float in an rtl block
-        nscoord inlineEndEdge = rect.IEnd(aWM);
-        if (inlineEndEdge > inlineStart) {
-          inlineStart = inlineEndEdge;
+      if (fi.mFrame->StyleDisplay()->mFloats == NS_STYLE_FLOAT_LEFT) {
+        // A left float
+        nscoord lineRightEdge = fi.LineRight();
+        if (lineRightEdge > lineLeft) {
+          lineLeft = lineRightEdge;
           // Only set haveFloats to true if the float is inside our
           // containing block.  This matches the spec for what some
           // callers want and disagrees for other callers, so we should
           // probably provide better information at some point.
           haveFloats = true;
         }
       } else {
-        // A left float in an rtl block or a right float in an ltr block
-        nscoord inlineStartEdge = rect.IStart(aWM);
-        if (inlineStartEdge < inlineEnd) {
-          inlineEnd = inlineStartEdge;
+        // A right float
+        nscoord lineLeftEdge = fi.LineLeft();
+        if (lineLeftEdge < lineRight) {
+          lineRight = lineLeftEdge;
           // See above.
           haveFloats = true;
         }
       }
     }
   }
 
   nscoord blockSize = (blockEnd == nscoord_MAX) ?
                        nscoord_MAX : (blockEnd - blockStart);
-  return nsFlowAreaRect(aWM,
-                        inlineStart - offset.I(aWM), blockStart - offset.B(aWM),
-                        inlineEnd - inlineStart, blockSize, haveFloats);
+  // convert back from LineLeft/Right to IStart
+  nscoord inlineStart = aWM.IsVertical() || aWM.IsBidiLTR()
+                         ? lineLeft - mLineLeft
+                         : mLineLeft + aContainerWidth - lineRight;
+
+  return nsFlowAreaRect(aWM, inlineStart, blockStart - mBlockStart,
+                        lineRight - lineLeft, blockSize, haveFloats);
 }
 
 nsresult
 nsFloatManager::AddFloat(nsIFrame* aFloatFrame, const LogicalRect& aMarginRect,
                          WritingMode aWM, nscoord aContainerWidth)
 {
   NS_ASSERTION(aMarginRect.ISize(aWM) >= 0, "negative inline size!");
   NS_ASSERTION(aMarginRect.BSize(aWM) >= 0, "negative block size!");
 
-  FloatInfo info(aFloatFrame, aWM, aMarginRect + mOffset);
+  FloatInfo info(aFloatFrame,
+                 aMarginRect.LineLeft(aWM, aContainerWidth) + mLineLeft,
+                 aMarginRect.BStart(aWM) + mBlockStart,
+                 aMarginRect.ISize(aWM),
+                 aMarginRect.BSize(aWM));
 
   // Set mLeftBEnd and mRightBEnd.
   if (HasAnyFloats()) {
     FloatInfo &tail = mFloats[mFloats.Length() - 1];
     info.mLeftBEnd = tail.mLeftBEnd;
     info.mRightBEnd = tail.mRightBEnd;
   } else {
     info.mLeftBEnd = nscoord_MIN;
     info.mRightBEnd = nscoord_MIN;
   }
   uint8_t floatStyle = aFloatFrame->StyleDisplay()->mFloats;
   NS_ASSERTION(floatStyle == NS_STYLE_FLOAT_LEFT ||
                floatStyle == NS_STYLE_FLOAT_RIGHT, "unexpected float");
-  nscoord& sideBEnd =
-    ((floatStyle == NS_STYLE_FLOAT_LEFT) == aWM.IsBidiLTR()) ? info.mLeftBEnd
-                                                             : info.mRightBEnd;
-  nscoord thisBEnd = info.mRect.BEnd(aWM);
+  nscoord& sideBEnd = floatStyle == NS_STYLE_FLOAT_LEFT ? info.mLeftBEnd
+                                                        : info.mRightBEnd;
+  nscoord thisBEnd = info.BEnd();
   if (thisBEnd > sideBEnd)
     sideBEnd = thisBEnd;
 
   if (!mFloats.AppendElement(info))
     return NS_ERROR_OUT_OF_MEMORY;
 
   return NS_OK;
 }
 
+// static
 LogicalRect
 nsFloatManager::CalculateRegionFor(WritingMode          aWM,
                                    nsIFrame*            aFloat,
                                    const LogicalMargin& aMargin,
                                    nscoord              aContainerWidth)
 {
   // We consider relatively positioned frames at their original position.
   LogicalRect region(aWM, nsRect(aFloat->GetNormalPosition(),
@@ -397,56 +401,53 @@ nsFloatManager::PushState(SavedState* aS
   // intentionally not saved or restored in PushState() and PopState(),
   // since that could lead to bugs where damage is missed/dropped when
   // we move from position A to B (during the intermediate incremental
   // reflow mentioned above) and then from B to C during the subsequent
   // reflow. In the typical case A and C will be the same, but not always.
   // Allowing mFloatDamage to accumulate the damage incurred during both
   // reflows ensures that nothing gets missed.
   aState->mWritingMode = mWritingMode;
-  aState->mOffset = mOffset;
+  aState->mLineLeft = mLineLeft;
+  aState->mBlockStart = mBlockStart;
   aState->mPushedLeftFloatPastBreak = mPushedLeftFloatPastBreak;
   aState->mPushedRightFloatPastBreak = mPushedRightFloatPastBreak;
   aState->mSplitLeftFloatAcrossBreak = mSplitLeftFloatAcrossBreak;
   aState->mSplitRightFloatAcrossBreak = mSplitRightFloatAcrossBreak;
   aState->mFloatInfoCount = mFloats.Length();
 }
 
 void
 nsFloatManager::PopState(SavedState* aState)
 {
   NS_PRECONDITION(aState, "No state to restore?");
 
   mWritingMode = aState->mWritingMode;
-  mOffset = aState->mOffset;
+  mLineLeft = aState->mLineLeft;
+  mBlockStart = aState->mBlockStart;
   mPushedLeftFloatPastBreak = aState->mPushedLeftFloatPastBreak;
   mPushedRightFloatPastBreak = aState->mPushedRightFloatPastBreak;
   mSplitLeftFloatAcrossBreak = aState->mSplitLeftFloatAcrossBreak;
   mSplitRightFloatAcrossBreak = aState->mSplitRightFloatAcrossBreak;
 
   NS_ASSERTION(aState->mFloatInfoCount <= mFloats.Length(),
                "somebody misused PushState/PopState");
   mFloats.TruncateLength(aState->mFloatInfoCount);
 }
 
 nscoord
-nsFloatManager::GetLowestFloatTop(WritingMode aWM,
-                                  nscoord aContainerWidth) const
+nsFloatManager::GetLowestFloatTop() const
 {
   if (mPushedLeftFloatPastBreak || mPushedRightFloatPastBreak) {
     return nscoord_MAX;
   }
   if (!HasAnyFloats()) {
     return nscoord_MIN;
   }
-  FloatInfo fi = mFloats[mFloats.Length() - 1];
-  LogicalRect rect = fi.mRect.ConvertTo(aWM, fi.mWritingMode, aContainerWidth);
-  LogicalPoint offset = mOffset.ConvertTo(aWM, mWritingMode, 0);
-
-  return rect.BStart(aWM) - offset.B(aWM);
+  return mFloats[mFloats.Length() -1].BStart() - mBlockStart;
 }
 
 #ifdef DEBUG_FRAME_DUMP
 void
 DebugListFloatManager(const nsFloatManager *aFloatManager)
 {
   aFloatManager->List(stdout);
 }
@@ -456,61 +457,54 @@ nsFloatManager::List(FILE* out) const
 {
   if (!HasAnyFloats())
     return NS_OK;
 
   for (uint32_t i = 0; i < mFloats.Length(); ++i) {
     const FloatInfo &fi = mFloats[i];
     fprintf_stderr(out, "Float %u: frame=%p rect={%d,%d,%d,%d} ymost={l:%d, r:%d}\n",
                    i, static_cast<void*>(fi.mFrame),
-                   fi.mRect.IStart(fi.mWritingMode),
-                   fi.mRect.BStart(fi.mWritingMode),
-                   fi.mRect.ISize(fi.mWritingMode),
-                   fi.mRect.BSize(fi.mWritingMode),
+                   fi.LineLeft(), fi.BStart(), fi.ISize(), fi.BSize(),
                    fi.mLeftBEnd, fi.mRightBEnd);
   }
   return NS_OK;
 }
 #endif
 
 nscoord
-nsFloatManager::ClearFloats(WritingMode aWM, nscoord aBCoord,
-                            uint8_t aBreakType, nscoord aContainerWidth,
+nsFloatManager::ClearFloats(nscoord aBCoord, uint8_t aBreakType,
                             uint32_t aFlags) const
 {
   if (!(aFlags & DONT_CLEAR_PUSHED_FLOATS) && ClearContinues(aBreakType)) {
     return nscoord_MAX;
   }
   if (!HasAnyFloats()) {
     return aBCoord;
   }
 
-  LogicalPoint offset = mOffset.ConvertTo(aWM, mWritingMode, 0);
-  nscoord blockEnd = aBCoord + offset.B(aWM);
+  nscoord blockEnd = aBCoord + mBlockStart;
 
   const FloatInfo &tail = mFloats[mFloats.Length() - 1];
   switch (aBreakType) {
     case NS_STYLE_CLEAR_BOTH:
       blockEnd = std::max(blockEnd, tail.mLeftBEnd);
       blockEnd = std::max(blockEnd, tail.mRightBEnd);
       break;
     case NS_STYLE_CLEAR_LEFT:
-      blockEnd = std::max(blockEnd, aWM.IsBidiLTR() ? tail.mLeftBEnd
-                                                    : tail.mRightBEnd);
+      blockEnd = std::max(blockEnd, tail.mLeftBEnd);
       break;
     case NS_STYLE_CLEAR_RIGHT:
-      blockEnd = std::max(blockEnd, aWM.IsBidiLTR() ? tail.mRightBEnd
-                                                    : tail.mLeftBEnd);
+      blockEnd = std::max(blockEnd, tail.mRightBEnd);
       break;
     default:
       // Do nothing
       break;
   }
 
-  blockEnd -= offset.B(aWM);
+  blockEnd -= mBlockStart;
 
   return blockEnd;
 }
 
 bool
 nsFloatManager::ClearContinues(uint8_t aBreakType) const
 {
   return ((mPushedLeftFloatPastBreak || mSplitLeftFloatAcrossBreak) &&
@@ -519,30 +513,31 @@ nsFloatManager::ClearContinues(uint8_t a
          ((mPushedRightFloatPastBreak || mSplitRightFloatAcrossBreak) &&
           (aBreakType == NS_STYLE_CLEAR_BOTH ||
            aBreakType == NS_STYLE_CLEAR_RIGHT));
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // FloatInfo
 
-nsFloatManager::FloatInfo::FloatInfo(nsIFrame* aFrame, WritingMode aWM,
-                                     const LogicalRect& aRect)
-  : mFrame(aFrame), mRect(aRect), mWritingMode(aWM)
+nsFloatManager::FloatInfo::FloatInfo(nsIFrame* aFrame,
+                                     nscoord aLineLeft, nscoord aBStart,
+                                     nscoord aISize, nscoord aBSize)
+  : mFrame(aFrame)
+  , mRect(aLineLeft, aBStart, aISize, aBSize)
 {
   MOZ_COUNT_CTOR(nsFloatManager::FloatInfo);
 }
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsFloatManager::FloatInfo::FloatInfo(const FloatInfo& aOther)
   : mFrame(aOther.mFrame),
-    mRect(aOther.mRect),
-    mWritingMode(aOther.mWritingMode),
     mLeftBEnd(aOther.mLeftBEnd),
-    mRightBEnd(aOther.mRightBEnd)
+    mRightBEnd(aOther.mRightBEnd),
+    mRect(aOther.mRect)
 {
   MOZ_COUNT_CTOR(nsFloatManager::FloatInfo);
 }
 
 nsFloatManager::FloatInfo::~FloatInfo()
 {
   MOZ_COUNT_DTOR(nsFloatManager::FloatInfo);
 }
--- a/layout/generic/nsFloatManager.h
+++ b/layout/generic/nsFloatManager.h
@@ -81,77 +81,50 @@ public:
   static void StoreRegionFor(mozilla::WritingMode aWM,
                              nsIFrame* aFloat,
                              const mozilla::LogicalRect& aRegion,
                              nscoord aContainerWidth);
 
   // Structure that stores the current state of a frame manager for
   // Save/Restore purposes.
   struct SavedState {
-    explicit SavedState(mozilla::WritingMode aWM)
-      : mWritingMode(aWM)
-      , mOffset(aWM)
-    {}
+    explicit SavedState() {}
   private:
     uint32_t mFloatInfoCount;
     mozilla::WritingMode mWritingMode;
-    mozilla::LogicalPoint mOffset;
+    nscoord mLineLeft, mBlockStart;
     bool mPushedLeftFloatPastBreak;
     bool mPushedRightFloatPastBreak;
     bool mSplitLeftFloatAcrossBreak;
     bool mSplitRightFloatAcrossBreak;
 
     friend class nsFloatManager;
   };
 
   /**
-   * Translate the current offset by the specified (dICoord, dBCoord). This
+   * Translate the current origin by the specified offsets. This
    * creates a new local coordinate space relative to the current
    * coordinate space.
    * @returns previous writing mode
    */
-  mozilla::WritingMode Translate(mozilla::WritingMode aWM,
-                                 mozilla::LogicalPoint aDOffset)
+  void Translate(nscoord aLineLeft, nscoord aBlockStart)
   {
-    mozilla::WritingMode oldWM = mWritingMode;
-    mOffset = mOffset.ConvertTo(aWM, oldWM, 0);
-    mWritingMode = aWM;
-    mOffset += aDOffset;
-    return oldWM;
-  }
-
-  /*
-   * Set the translation offset to a specified value instead of
-   * translating by a delta.
-   */
-  void SetTranslation(mozilla::WritingMode aWM,
-                      mozilla::LogicalPoint aOffset)
-  {
-    mWritingMode = aWM;
-    mOffset = aOffset;
-  }
-
-  void Untranslate(mozilla::WritingMode aWM,
-                   mozilla::LogicalPoint aDOffset)
-  {
-    mOffset -= aDOffset;
-    mOffset = mOffset.ConvertTo(aWM, mWritingMode, 0);
-    mWritingMode = aWM;
+    mLineLeft += aLineLeft;
+    mBlockStart += aBlockStart;
   }
 
   /**
    * Returns the current translation from local coordinate space to
    * world coordinate space. This represents the accumulated calls to
    * Translate().
    */
-  void GetTranslation(mozilla::WritingMode& aWM,
-                      mozilla::LogicalPoint& aOffset) const
+  void GetTranslation(nscoord& aLineLeft, nscoord& aBlockStart) const
   {
-    aWM = mWritingMode;
-    aOffset = mOffset;
+    aLineLeft = mLineLeft;
+    aBlockStart = mBlockStart;
   }
 
   /**
    * Get information about the area available to content that flows
    * around floats.  Two different types of space can be requested:
    *   BAND_FROM_POINT: returns the band containing block-dir coordinate
    *     |aBCoord| (though actually with the top truncated to begin at
    *     aBCoord), but up to at most |aBSize| (which may be nscoord_MAX).
@@ -250,25 +223,25 @@ public:
   bool HasFloatDamage() const
   {
     return !mFloatDamage.IsEmpty();
   }
 
   void IncludeInDamage(mozilla::WritingMode aWM,
                        nscoord aIntervalBegin, nscoord aIntervalEnd)
   {
-    mFloatDamage.IncludeInterval(aIntervalBegin + mOffset.B(aWM),
-                                 aIntervalEnd + mOffset.B(aWM));
+    mFloatDamage.IncludeInterval(aIntervalBegin + mBlockStart,
+                                 aIntervalEnd + mBlockStart);
   }
 
   bool IntersectsDamage(mozilla::WritingMode aWM,
                         nscoord aIntervalBegin, nscoord aIntervalEnd) const
   {
-    return mFloatDamage.Intersects(aIntervalBegin + mOffset.B(aWM),
-                                   aIntervalEnd + mOffset.B(aWM));
+    return mFloatDamage.Intersects(aIntervalBegin + mBlockStart,
+                                   aIntervalEnd + mBlockStart);
   }
 
   /**
    * Saves the current state of the float manager into aState.
    */
   void PushState(SavedState* aState);
 
   /**
@@ -285,45 +258,44 @@ public:
 
   /**
    * Get the block start of the last float placed into the float
    * manager, to enforce the rule that a float can't be above an earlier
    * float. Returns the minimum nscoord value if there are no floats.
    *
    * The result is relative to the current translation.
    */
-  nscoord GetLowestFloatTop(mozilla::WritingMode aWM,
-                            nscoord aContainerWidth) const;
+  nscoord GetLowestFloatTop() const;
 
   /**
    * Return the coordinate of the lowest float matching aBreakType in
    * this float manager. Returns aBCoord if there are no matching
    * floats.
    *
    * Both aBCoord and the result are relative to the current translation.
    */
   enum {
     // Tell ClearFloats not to push to nscoord_MAX when floats have been
     // pushed to the next page/column.
     DONT_CLEAR_PUSHED_FLOATS = (1<<0)
   };
-  nscoord ClearFloats(mozilla::WritingMode aWM, nscoord aBCoord,
-                      uint8_t aBreakType, nscoord aContainerWidth,
+  nscoord ClearFloats(nscoord aBCoord, uint8_t aBreakType,
                       uint32_t aFlags = 0) const;
 
   /**
    * Checks if clear would pass into the floats' BFC's next-in-flow,
    * i.e. whether floats affecting this clear have continuations.
    */
   bool ClearContinues(uint8_t aBreakType) const;
 
   void AssertStateMatches(SavedState *aState) const
   {
     NS_ASSERTION(aState->mWritingMode == mWritingMode &&
-                 aState->mOffset == mOffset &&
+                 aState->mLineLeft == mLineLeft &&
+                 aState->mBlockStart == mBlockStart &&
                  aState->mPushedLeftFloatPastBreak ==
                    mPushedLeftFloatPastBreak &&
                  aState->mPushedRightFloatPastBreak ==
                    mPushedRightFloatPastBreak &&
                  aState->mSplitLeftFloatAcrossBreak ==
                    mSplitLeftFloatAcrossBreak &&
                  aState->mSplitRightFloatAcrossBreak ==
                    mSplitRightFloatAcrossBreak &&
@@ -337,34 +309,50 @@ public:
    */
   nsresult List(FILE* out) const;
 #endif
 
 private:
 
   struct FloatInfo {
     nsIFrame *const mFrame;
-    mozilla::LogicalRect mRect;
-    mozilla::WritingMode mWritingMode;
     // The lowest block-ends of left/right floats up to and including
     // this one.
     nscoord mLeftBEnd, mRightBEnd;
 
-    FloatInfo(nsIFrame* aFrame, mozilla::WritingMode aWM,
-              const mozilla::LogicalRect& aRect);
+    FloatInfo(nsIFrame* aFrame, nscoord aLineLeft, nscoord aBStart,
+              nscoord aISize, nscoord aBSize);
+
+    nscoord LineLeft() const { return mRect.x; }
+    nscoord LineRight() const { return mRect.XMost(); }
+    nscoord ISize() const { return mRect.width; }
+    nscoord BStart() const { return mRect.y; }
+    nscoord BEnd() const { return mRect.YMost(); }
+    nscoord BSize() const { return mRect.height; }
+    bool IsEmpty() const { return mRect.IsEmpty(); }
+
 #ifdef NS_BUILD_REFCNT_LOGGING
     FloatInfo(const FloatInfo& aOther);
     ~FloatInfo();
 #endif
+
+  private:
+    // NB! This is really a logical rect in a writing mode suitable for
+    // placing floats, which is not necessarily the actual writing mode
+    // either of the block which created the frame manager or the block
+    // that is calling the frame manager. The inline coordinates are in
+    // the line-relative axis of the frame manager and its block
+    // coordinates are in the frame manager's real block direction.
+    nsRect mRect;
   };
 
   mozilla::WritingMode mWritingMode;
-  mozilla::LogicalPoint mOffset;  // translation from local to global
-                                  // coordinate space
 
+  // Translation from local to global coordinate space.
+  nscoord mLineLeft, mBlockStart;
   nsTArray<FloatInfo> mFloats;
   nsIntervalSet   mFloatDamage;
 
   // Did we try to place a float that could not fit at all and had to be
   // pushed to the next page/column?  If so, we can't place any more
   // floats in this page/column because of the rule that the top of a
   // float cannot be above the top of an earlier float.  And we also
   // need to apply this information to 'clear', and thus need to
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -937,18 +937,19 @@ nsLineLayout::ReflowFrame(nsIFrame* aFra
   aFrame->WillReflow(mPresContext);
 
   // Adjust spacemanager coordinate system for the frame.
   nsHTMLReflowMetrics metrics(lineWM);
 #ifdef DEBUG
   metrics.ISize(lineWM) = nscoord(0xdeadbeef);
   metrics.BSize(lineWM) = nscoord(0xdeadbeef);
 #endif
-  LogicalPoint tPt = pfd->mBounds.Origin(lineWM);
-  WritingMode oldWM = mFloatManager->Translate(lineWM, tPt);
+  nscoord tI = pfd->mBounds.LineLeft(lineWM, ContainerWidth());
+  nscoord tB = pfd->mBounds.BStart(lineWM);
+  mFloatManager->Translate(tI, tB);
 
   int32_t savedOptionalBreakOffset;
   gfxBreakPriority savedOptionalBreakPriority;
   nsIFrame* savedOptionalBreakFrame =
     GetLastOptionalBreakPosition(&savedOptionalBreakOffset,
                                  &savedOptionalBreakPriority);
 
   if (!isText) {
@@ -1024,17 +1025,17 @@ nsLineLayout::ReflowFrame(nsIFrame* aFra
         isEmpty = !pfd->mSpan->mHasNonemptyContent && pfd->mFrame->IsSelfEmpty();
       } else {
         isEmpty = pfd->mFrame->IsEmpty();
       }
     }
   }
   pfd->mIsEmpty = isEmpty;
 
-  mFloatManager->Untranslate(oldWM, tPt);
+  mFloatManager->Translate(-tI, -tB);
 
   NS_ASSERTION(metrics.ISize(lineWM) >= 0, "bad inline size");
   NS_ASSERTION(metrics.BSize(lineWM) >= 0,"bad block size");
   if (metrics.ISize(lineWM) < 0) {
     metrics.ISize(lineWM) = 0;
   }
   if (metrics.BSize(lineWM) < 0) {
     metrics.BSize(lineWM) = 0;
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -251,16 +251,30 @@ public:
       return;
     }
     if (mRenderFrame) {
       TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
       browser->NotifyAPZStateChange(aGuid.mScrollId, aChange, aArg);
     }
   }
 
+  void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent) override {
+    if (MessageLoop::current() != mUILoop) {
+      mUILoop->PostTask(
+        FROM_HERE,
+        NewRunnableMethod(this, &RemoteContentController::NotifyMozMouseScrollEvent, aScrollId, aEvent));
+      return;
+    }
+
+    if (mRenderFrame) {
+      TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
+      browser->NotifyMouseScrollTestEvent(aScrollId, aEvent);
+    }
+  }
+
   // Methods used by RenderFrameParent to set fields stored here.
 
   void SaveZoomConstraints(const ZoomConstraints& aConstraints)
   {
     mHaveZoomConstraints = true;
     mZoomConstraints = aConstraints;
   }
 
--- a/layout/reftests/floats/float-in-rtl-1a.html
+++ b/layout/reftests/floats/float-in-rtl-1a.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body>
 <div style="width:300px" dir="rtl">
-  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right"></div>
+  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right;position:relative;"></div>
 </div>
 <div style="width:200px">
-  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right"></div>
+  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right;position:relative;"></div>
 </div>
 <div style="background:silver">
 This text should appear to the LEFT of the red and green blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-1b.html
+++ b/layout/reftests/floats/float-in-rtl-1b.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body>
 <div style="width:300px">
-  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right"></div>
+  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right;position:relative;"></div>
 </div>
 <div style="width:200px" dir="rtl">
-  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right"></div>
+  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right;position:relative;"></div>
 </div>
 <div style="background:silver">
 This text should appear to the LEFT of the red and green blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-1c.html
+++ b/layout/reftests/floats/float-in-rtl-1c.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body>
 <div style="width:300px" dir="rtl">
-  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right"></div>
+  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right;position:relative;"></div>
 </div>
 <div style="width:200px" dir="rtl">
-  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right"></div>
+  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right;position:relative;"></div>
 </div>
 <div style="background:silver">
 This text should appear to the LEFT of the red and green blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-1d.html
+++ b/layout/reftests/floats/float-in-rtl-1d.html
@@ -1,19 +1,19 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body>
 <div dir="rtl" style="width:300px">
   <div style="width:300px">
-    <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right"></div>
+    <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right;position:relative;"></div>
   </div>
   <div style="width:200px">
-    <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right"></div>
+    <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right;position:relative;"></div>
   </div>
 </div>
 <div style="background:silver">
 This text should appear to the LEFT of the red and green blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-2a.html
+++ b/layout/reftests/floats/float-in-rtl-2a.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body>
 <div dir="rtl" style="width:300px">
-  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left"></div>
+  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left;position:relative;"></div>
 </div>
 <div style="width:200px">
-  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left"></div>
+  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left;position:relative;"></div>
 </div>
 <div style="background:silver">
 This text should appear to the RIGHT of the green and red blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-2b.html
+++ b/layout/reftests/floats/float-in-rtl-2b.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body>
 <div style="width:300px">
-  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left"></div>
+  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left;position:relative;"></div>
 </div>
 <div dir="rtl" style="width:200px">
-  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left"></div>
+  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left;position:relative;"></div>
 </div>
 <div style="background:silver">
 This text should appear to the RIGHT of the green and red blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-2c.html
+++ b/layout/reftests/floats/float-in-rtl-2c.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body>
 <div dir="rtl" style="width:300px">
-  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left"></div>
+  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left;position:relative;"></div>
 </div>
 <div dir="rtl" style="width:200px">
-  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left"></div>
+  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left;position:relative;"></div>
 </div>
 <div style="background:silver">
 This text should appear to the RIGHT of the green and red blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-2d.html
+++ b/layout/reftests/floats/float-in-rtl-2d.html
@@ -1,19 +1,19 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body>
 <div dir="rtl" style="width:-moz-fit-content">
   <div style="width:300px">
-    <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left"></div>
+    <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left;position:relative;"></div>
   </div>
   <div style="width:200px">
-    <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left"></div>
+    <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left;position:relative;"></div>
   </div>
 </div>
 <div style="background:silver">
 This text should appear to the RIGHT of the green and red blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-3a.html
+++ b/layout/reftests/floats/float-in-rtl-3a.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body dir="rtl">
 <div dir="ltr" style="width:300px">
-  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left"></div>
+  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left;position:relative;"></div>
 </div>
 <div style="width:200px">
-  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left"></div>
+  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left;position:relative;"></div>
 </div>
 <div style="background:silver">
 This text should appear to the RIGHT of the green and red blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-3b.html
+++ b/layout/reftests/floats/float-in-rtl-3b.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body dir="rtl">
 <div style="width:300px">
-  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left"></div>
+  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left;position:relative;"></div>
 </div>
 <div dir="ltr" style="width:200px">
-  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left"></div>
+  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left;position:relative;"></div>
 </div>
 <div style="background:silver">
 This text should appear to the RIGHT of the green and red blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-3c.html
+++ b/layout/reftests/floats/float-in-rtl-3c.html
@@ -1,16 +1,16 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body dir="rtl">
 <div dir="ltr" style="width:300px">
-  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left"></div>
+  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left;position:relative;"></div>
 </div>
 <div dir="ltr" style="width:200px">
   <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left"></div>
 </div>
 <div style="background:silver">
 This text should appear to the RIGHT of the green and red blocks.
 </div>
 </body>
--- a/layout/reftests/floats/float-in-rtl-3d.html
+++ b/layout/reftests/floats/float-in-rtl-3d.html
@@ -1,19 +1,19 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body dir="rtl">
 <div dir="ltr" style="width:-moz-fit-content">
   <div style="width:300px">
-    <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left"></div>
+    <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:left;position:relative;"></div>
   </div>
   <div style="width:200px">
-    <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left"></div>
+    <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:left;position:relative;"></div>
   </div>
 </div>
 <div style="background:silver">
 This text should appear to the RIGHT of the green and red blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-4a.html
+++ b/layout/reftests/floats/float-in-rtl-4a.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body dir="rtl">
 <div dir="ltr" style="width:300px">
-  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right"></div>
+  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right;position:relative;"></div>
 </div>
 <div style="width:200px">
-  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right"></div>
+  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right;position:relative;"></div>
 </div>
 <div style="background:silver">
 This text should appear to the LEFT of the red and green blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-4b.html
+++ b/layout/reftests/floats/float-in-rtl-4b.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body dir="rtl">
 <div style="width:300px">
-  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right"></div>
+  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right;position:relative;"></div>
 </div>
 <div dir="ltr" style="width:200px">
-  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right"></div>
+  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right;position:relative;"></div>
 </div>
 <div style="background:silver">
 This text should appear to the LEFT of the red and green blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-4c.html
+++ b/layout/reftests/floats/float-in-rtl-4c.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body dir="rtl">
 <div dir="ltr" style="width:300px">
-  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right"></div>
+  <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right;position:relative;"></div>
 </div>
 <div dir="ltr" style="width:200px">
-  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right"></div>
+  <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right;position:relative;"></div>
 </div>
 <div style="background:silver">
 This text should appear to the LEFT of the red and green blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/float-in-rtl-4d.html
+++ b/layout/reftests/floats/float-in-rtl-4d.html
@@ -1,19 +1,19 @@
 <!DOCTYPE html>
 <html>
 <head>
 <title>Bug 1114329 testcase</title>
 </head>
 <body dir="rtl">
 <div dir="ltr" style="width:-moz-fit-content">
   <div style="width:300px">
-    <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right"></div>
+    <div style="width:100px;height:120px;background:rgba(0,255,0,0.8);float:right;position:relative;"></div>
   </div>
   <div style="width:200px">
-    <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right"></div>
+    <div style="width:100px;height:150px;background:rgba(255,0,0,0.8);float:right;position:relative;"></div>
   </div>
 </div>
 <div style="background:silver">
 This text should appear to the LEFT of the red and green blocks.
 </div>
 </body>
 </html>
--- a/layout/reftests/floats/reftest.list
+++ b/layout/reftests/floats/reftest.list
@@ -14,24 +14,24 @@ fails == 345369-1.html 345369-1-ref.html
 fails == 345369-2.html 345369-2-ref.html
 == 345369-3.html 345369-3-ref.html
 == 345369-4.html 345369-4-ref.html
 == 345369-5.html 345369-5-ref.html
 == 429974-1.html 429974-1-ref.html
 == 546048-1.html 546048-1-ref.html
 == 775350-1.html 775350-1-ref.html
 == 1114329.html 1114329-ref.html
-fails == float-in-rtl-1a.html float-in-rtl-1-ref.html # bug 1114329
-fails == float-in-rtl-1b.html float-in-rtl-1-ref.html # bug 1114329
-fails == float-in-rtl-1c.html float-in-rtl-1-ref.html # bug 1114329
-fails == float-in-rtl-1d.html float-in-rtl-1-ref.html # bug 1114329
-fails == float-in-rtl-2a.html float-in-rtl-2-ref.html # bug 1114329
-fails == float-in-rtl-2b.html float-in-rtl-2-ref.html # bug 1114329
-fails == float-in-rtl-2c.html float-in-rtl-2-ref.html # bug 1114329
-fails == float-in-rtl-2d.html float-in-rtl-2-ref.html # bug 1114329
-fails == float-in-rtl-3a.html float-in-rtl-3-ref.html # bug 1114329
-fails == float-in-rtl-3b.html float-in-rtl-3-ref.html # bug 1114329
-fails == float-in-rtl-3c.html float-in-rtl-3-ref.html # bug 1114329
-fails == float-in-rtl-3d.html float-in-rtl-3-ref.html # bug 1114329
-fails == float-in-rtl-4a.html float-in-rtl-4-ref.html # bug 1114329
-fails == float-in-rtl-4b.html float-in-rtl-4-ref.html # bug 1114329
-fails == float-in-rtl-4c.html float-in-rtl-4-ref.html # bug 1114329
-fails == float-in-rtl-4d.html float-in-rtl-4-ref.html # bug 1114329
+== float-in-rtl-1a.html float-in-rtl-1-ref.html
+== float-in-rtl-1b.html float-in-rtl-1-ref.html
+== float-in-rtl-1c.html float-in-rtl-1-ref.html
+== float-in-rtl-1d.html float-in-rtl-1-ref.html
+== float-in-rtl-2a.html float-in-rtl-2-ref.html
+== float-in-rtl-2b.html float-in-rtl-2-ref.html
+== float-in-rtl-2c.html float-in-rtl-2-ref.html
+== float-in-rtl-2d.html float-in-rtl-2-ref.html
+== float-in-rtl-3a.html float-in-rtl-3-ref.html
+== float-in-rtl-3b.html float-in-rtl-3-ref.html
+== float-in-rtl-3c.html float-in-rtl-3-ref.html
+== float-in-rtl-3d.html float-in-rtl-3-ref.html
+== float-in-rtl-4a.html float-in-rtl-4-ref.html
+== float-in-rtl-4b.html float-in-rtl-4-ref.html
+== float-in-rtl-4c.html float-in-rtl-4-ref.html
+== float-in-rtl-4d.html float-in-rtl-4-ref.html
--- a/media/gmp-clearkey/0.1/AudioDecoder.cpp
+++ b/media/gmp-clearkey/0.1/AudioDecoder.cpp
@@ -25,16 +25,17 @@
 using namespace wmf;
 
 AudioDecoder::AudioDecoder(GMPAudioHost *aHostAPI)
   : mHostAPI(aHostAPI)
   , mCallback(nullptr)
   , mWorkerThread(nullptr)
   , mMutex(nullptr)
   , mNumInputTasks(0)
+  , mHasShutdown(false)
 {
 }
 
 AudioDecoder::~AudioDecoder()
 {
   mMutex->Destroy();
 }
 
@@ -113,17 +114,17 @@ AudioDecoder::DecodeTask(GMPAudioSamples
   if (crypto) {
     // Plugin host should have set up its decryptor/key sessions
     // before trying to decode!
     GMPErr rv =
       ClearKeyDecryptionManager::Get()->Decrypt(&buffer[0], buffer.size(), crypto);
 
     if (GMP_FAILED(rv)) {
       CK_LOGE("Failed to decrypt with key id %08x...", *(uint32_t*)crypto->KeyId());
-      GetPlatform()->runonmainthread(WrapTask(mCallback, &GMPAudioDecoderCallback::Error, rv));
+      MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::Error, rv));
       return;
     }
   }
 
   hr = mDecoder->Input(&buffer[0],
                        buffer.size(),
                        aInput->TimeStamp());
 
@@ -145,17 +146,17 @@ AudioDecoder::DecodeTask(GMPAudioSamples
     if (hr == S_OK) {
       ReturnOutput(output);
     }
     if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
       AutoLock lock(mMutex);
       if (mNumInputTasks == 0) {
         // We have run all input tasks. We *must* notify Gecko so that it will
         // send us more data.
-        GetPlatform()->runonmainthread(WrapTask(mCallback, &GMPAudioDecoderCallback::InputDataExhausted));
+        MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::InputDataExhausted));
       }
     } else if (FAILED(hr)) {
       LOG("AudioDecoder::DecodeTask() output failed hr=0x%x\n", hr);
     }
   }
 }
 
 void
@@ -176,17 +177,17 @@ AudioDecoder::ReturnOutput(IMFSample* aS
   hr = MFToGMPSample(aSample, samples);
   if (FAILED(hr)) {
     samples->Destroy();
     LOG("Failed to prepare output sample!");
     return;
   }
   ENSURE(SUCCEEDED(hr), /*void*/);
 
-  GetPlatform()->runonmainthread(WrapTask(mCallback, &GMPAudioDecoderCallback::Decoded, samples));
+  MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::Decoded, samples));
 }
 
 HRESULT
 AudioDecoder::MFToGMPSample(IMFSample* aInput,
                             GMPAudioSamples* aOutput)
 {
   ENSURE(aInput != nullptr, E_POINTER);
   ENSURE(aOutput != nullptr, E_POINTER);
@@ -236,27 +237,70 @@ AudioDecoder::DrainTask()
   while (hr == S_OK) {
     CComPtr<IMFSample> output;
     hr = mDecoder->Output(&output);
     SAMPLE_LOG("AudioDecoder::DrainTask() output ret=0x%x\n", hr);
     if (hr == S_OK) {
       ReturnOutput(output);
     }
   }
-  GetPlatform()->runonmainthread(WrapTask(mCallback, &GMPAudioDecoderCallback::DrainComplete));
+  MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::DrainComplete));
 }
 
 void
 AudioDecoder::Drain()
 {
   EnsureWorker();
   mWorkerThread->Post(WrapTask(this,
                                &AudioDecoder::DrainTask));
 }
 
 void
 AudioDecoder::DecodingComplete()
 {
   if (mWorkerThread) {
     mWorkerThread->Join();
   }
+  mHasShutdown = true;
+
+  // Worker thread might have dispatched more tasks to the main thread that need this object.
+  // Append another task to delete |this|.
+  GetPlatform()->runonmainthread(WrapTask(this, &AudioDecoder::Destroy));
+}
+
+void
+AudioDecoder::Destroy()
+{
   delete this;
 }
+
+void
+AudioDecoder::MaybeRunOnMainThread(gmp_task_args_base* aTask)
+{
+  class MaybeRunTask : public GMPTask
+  {
+  public:
+    MaybeRunTask(AudioDecoder* aDecoder, gmp_task_args_base* aTask)
+      : mDecoder(aDecoder), mTask(aTask)
+    { }
+
+    virtual void Run(void) {
+      if (mDecoder->HasShutdown()) {
+        CK_LOGD("Trying to dispatch to main thread after AudioDecoder has shut down");
+        return;
+      }
+
+      mTask->Run();
+    }
+
+    virtual void Destroy()
+    {
+      mTask->Destroy();
+      delete this;
+    }
+
+  private:
+    AudioDecoder* mDecoder;
+    gmp_task_args_base* mTask;
+  };
+
+  GetPlatform()->runonmainthread(new MaybeRunTask(this, aTask));
+}
--- a/media/gmp-clearkey/0.1/AudioDecoder.h
+++ b/media/gmp-clearkey/0.1/AudioDecoder.h
@@ -14,16 +14,17 @@
  * limitations under the License.
  */
 
 #ifndef __AudioDecoder_h__
 #define __AudioDecoder_h__
 
 #include "gmp-audio-decode.h"
 #include "gmp-audio-host.h"
+#include "gmp-task-utils.h"
 #include "WMFAACDecoder.h"
 
 #include "mfobjects.h"
 
 class AudioDecoder : public GMPAudioDecoder
 {
 public:
   AudioDecoder(GMPAudioHost *aHostAPI);
@@ -36,30 +37,37 @@ public:
   virtual void Decode(GMPAudioSamples* aEncodedSamples);
 
   virtual void Reset() override;
 
   virtual void Drain() override;
 
   virtual void DecodingComplete() override;
 
+  bool HasShutdown() { return mHasShutdown; }
+
 private:
 
   void EnsureWorker();
 
   void DecodeTask(GMPAudioSamples* aEncodedSamples);
   void DrainTask();
 
   void ReturnOutput(IMFSample* aSample);
 
   HRESULT MFToGMPSample(IMFSample* aSample,
                         GMPAudioSamples* aAudioFrame);
 
+  void MaybeRunOnMainThread(gmp_task_args_base* aTask);
+  void Destroy();
+
   GMPAudioHost *mHostAPI; // host-owned, invalid at DecodingComplete
   GMPAudioDecoderCallback* mCallback; // host-owned, invalid at DecodingComplete
   GMPThread* mWorkerThread;
   GMPMutex* mMutex;
   wmf::AutoPtr<wmf::WMFAACDecoder> mDecoder;
 
   int32_t mNumInputTasks;
+
+  bool mHasShutdown;
 };
 
 #endif // __AudioDecoder_h__
--- a/media/gmp-clearkey/0.1/VideoDecoder.cpp
+++ b/media/gmp-clearkey/0.1/VideoDecoder.cpp
@@ -29,16 +29,17 @@ using namespace wmf;
 
 VideoDecoder::VideoDecoder(GMPVideoHost *aHostAPI)
   : mHostAPI(aHostAPI)
   , mCallback(nullptr)
   , mWorkerThread(nullptr)
   , mMutex(nullptr)
   , mNumInputTasks(0)
   , mSentExtraData(false)
+  , mHasShutdown(false)
 {
 }
 
 VideoDecoder::~VideoDecoder()
 {
   mMutex->Destroy();
 }
 
@@ -155,17 +156,17 @@ VideoDecoder::DecodeTask(GMPVideoEncoded
   std::vector<uint8_t> buffer(inBuffer, inBuffer + aInput->Size());
   if (crypto) {
     // Plugin host should have set up its decryptor/key sessions
     // before trying to decode!
     GMPErr rv =
       ClearKeyDecryptionManager::Get()->Decrypt(&buffer[0], buffer.size(), crypto);
 
     if (GMP_FAILED(rv)) {
-      GetPlatform()->runonmainthread(WrapTask(mCallback, &GMPVideoDecoderCallback::Error, rv));
+      MaybeRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::Error, rv));
       return;
     }
   }
 
   AnnexB::ConvertFrameInPlace(buffer);
 
   if (aInput->FrameType() == kGMPKeyFrame) {
     // We must send the SPS and PPS to Windows Media Foundation's decoder.
@@ -187,31 +188,31 @@ VideoDecoder::DecodeTask(GMPVideoEncoded
     return;
   }
 
   while (hr == S_OK) {
     CComPtr<IMFSample> output;
     hr = mDecoder->Output(&output);
     CK_LOGD("VideoDecoder::DecodeTask() output ret=0x%x\n", hr);
     if (hr == S_OK) {
-      GetPlatform()->runonmainthread(
+      MaybeRunOnMainThread(
         WrapTask(this,
                  &VideoDecoder::ReturnOutput,
                  CComPtr<IMFSample>(mozilla::Move(output)),
                  mDecoder->GetFrameWidth(),
                  mDecoder->GetFrameHeight(),
                  mDecoder->GetStride()));
       assert(!output.Get());
     }
     if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
       AutoLock lock(mMutex);
       if (mNumInputTasks == 0) {
         // We have run all input tasks. We *must* notify Gecko so that it will
         // send us more data.
-        GetPlatform()->runonmainthread(
+        MaybeRunOnMainThread(
           WrapTask(mCallback,
                    &GMPVideoDecoderCallback::InputDataExhausted));
       }
     }
     if (FAILED(hr)) {
       CK_LOGE("VideoDecoder::DecodeTask() output failed hr=0x%x\n", hr);
     }
   }
@@ -342,37 +343,80 @@ VideoDecoder::DrainTask()
 
   // Return any pending output.
   HRESULT hr = S_OK;
   while (hr == S_OK) {
     CComPtr<IMFSample> output;
     hr = mDecoder->Output(&output);
     CK_LOGD("VideoDecoder::DrainTask() output ret=0x%x\n", hr);
     if (hr == S_OK) {
-      GetPlatform()->runonmainthread(
+      MaybeRunOnMainThread(
         WrapTask(this,
                  &VideoDecoder::ReturnOutput,
                  CComPtr<IMFSample>(mozilla::Move(output)),
                  mDecoder->GetFrameWidth(),
                  mDecoder->GetFrameHeight(),
                  mDecoder->GetStride()));
       assert(!output.Get());
     }
   }
-  GetPlatform()->runonmainthread(WrapTask(mCallback, &GMPVideoDecoderCallback::DrainComplete));
+  MaybeRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::DrainComplete));
 }
 
 void
 VideoDecoder::Drain()
 {
   EnsureWorker();
   mWorkerThread->Post(WrapTask(this,
                                &VideoDecoder::DrainTask));
 }
 
 void
 VideoDecoder::DecodingComplete()
 {
   if (mWorkerThread) {
     mWorkerThread->Join();
   }
+  mHasShutdown = true;
+
+  // Worker thread might have dispatched more tasks to the main thread that need this object.
+  // Append another task to delete |this|.
+  GetPlatform()->runonmainthread(WrapTask(this, &VideoDecoder::Destroy));
+}
+
+void
+VideoDecoder::Destroy()
+{
   delete this;
 }
+
+void
+VideoDecoder::MaybeRunOnMainThread(gmp_task_args_base* aTask)
+{
+  class MaybeRunTask : public GMPTask
+  {
+  public:
+    MaybeRunTask(VideoDecoder* aDecoder, gmp_task_args_base* aTask)
+      : mDecoder(aDecoder), mTask(aTask)
+    { }
+
+    virtual void Run(void) {
+      if (mDecoder->HasShutdown()) {
+        CK_LOGD("Trying to dispatch to main thread after VideoDecoder has shut down");
+        return;
+      }
+
+      mTask->Run();
+    }
+
+    virtual void Destroy()
+    {
+      mTask->Destroy();
+      delete this;
+    }
+
+  private:
+    VideoDecoder* mDecoder;
+    gmp_task_args_base* mTask;
+  };
+
+  GetPlatform()->runonmainthread(new MaybeRunTask(this, aTask));
+}
--- a/media/gmp-clearkey/0.1/VideoDecoder.h
+++ b/media/gmp-clearkey/0.1/VideoDecoder.h
@@ -12,16 +12,17 @@
  * 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.
  */
 
 #ifndef __VideoDecoder_h__
 #define __VideoDecoder_h__
 
+#include "gmp-task-utils.h"
 #include "gmp-video-decode.h"
 #include "gmp-video-host.h"
 #include "WMFH264Decoder.h"
 
 #include "mfobjects.h"
 
 class VideoDecoder : public GMPVideoDecoder
 {
@@ -43,16 +44,18 @@ public:
                       int64_t aRenderTimeMs = -1);
 
   virtual void Reset() override;
 
   virtual void Drain() override;
 
   virtual void DecodingComplete() override;
 
+  bool HasShutdown() { return mHasShutdown; }
+
 private:
 
   void EnsureWorker();
 
   void DrainTask();
 
   void DecodeTask(GMPVideoEncodedFrame* aInputFrame);
 
@@ -62,22 +65,27 @@ private:
                     int32_t aStride);
 
   HRESULT SampleToVideoFrame(IMFSample* aSample,
                              int32_t aWidth,
                              int32_t aHeight,
                              int32_t aStride,
                              GMPVideoi420Frame* aVideoFrame);
 
+  void MaybeRunOnMainThread(gmp_task_args_base* aTask);
+  void Destroy();
+
   GMPVideoHost *mHostAPI; // host-owned, invalid at DecodingComplete
   GMPVideoDecoderCallback* mCallback; // host-owned, invalid at DecodingComplete
   GMPThread* mWorkerThread;
   GMPMutex* mMutex;
   wmf::AutoPtr<wmf::WMFH264Decoder> mDecoder;
 
   std::vector<uint8_t> mExtraData;
   std::vector<uint8_t> mAnnexB;
 
   int32_t mNumInputTasks;
   bool mSentExtraData;
+
+  bool mHasShutdown;
 };
 
 #endif // __VideoDecoder_h__
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -131,17 +131,17 @@ InterceptStreamListener::OnDataAvailable
     mOwner->GetURI(getter_AddRefs(uri));
 
     nsAutoCString host;
     uri->GetHost(host);
 
     OnStatus(mOwner, aContext, NS_NET_STATUS_READING, NS_ConvertUTF8toUTF16(host).get());
 
     int64_t progress = aOffset + aCount;
-    OnProgress(mOwner, aContext, progress, mOwner->GetResponseHead()->ContentLength());
+    OnProgress(mOwner, aContext, progress, mOwner->mSynthesizedStreamLength);
   }
 
   mOwner->DoOnDataAvailable(mOwner, mContext, aInputStream, aOffset, aCount);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 InterceptStreamListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatusCode)
@@ -162,16 +162,17 @@ InterceptStreamListener::Cleanup()
 }
 
 //-----------------------------------------------------------------------------
 // HttpChannelChild
 //-----------------------------------------------------------------------------
 
 HttpChannelChild::HttpChannelChild()
   : HttpAsyncAborter<HttpChannelChild>(this)
+  , mSynthesizedStreamLength(0)
   , mIsFromCache(false)
   , mCacheEntryAvailable(false)
   , mCacheExpirationTime(nsICacheEntry::NO_EXPIRATION_TIME)
   , mSendResumeAt(false)
   , mIPCOpen(false)
   , mKeptAlive(false)
   , mDivertingToParent(false)
   , mFlushedForDiversion(false)
@@ -2078,20 +2079,22 @@ HttpChannelChild::ResetInterception()
 
   // Continue with the original cross-process request
   nsresult rv = ContinueAsyncOpen();
   NS_ENSURE_SUCCESS_VOID(rv);
 }
 
 void
 HttpChannelChild::OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>& aResponseHead,
-                                                  nsInputStreamPump* aPump)
+                                                  nsInputStreamPump* aPump,
+                                                  int64_t aStreamLength)
 {
   mSynthesizedResponsePump = aPump;
   mResponseHead = aResponseHead;
+  mSynthesizedStreamLength = aStreamLength;
 
   // if this channel has been suspended previously, the pump needs to be
   // correspondingly suspended now that it exists.
   for (uint32_t i = 0; i < mSuspendCount; i++) {
     nsresult rv = mSynthesizedResponsePump->Suspend();
     NS_ENSURE_SUCCESS_VOID(rv);
   }
 
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -154,22 +154,23 @@ private:
   void DoPreOnStopRequest(nsresult aStatus);
   void DoOnStopRequest(nsIRequest* aRequest, nsISupports* aContext);
 
   // Discard the prior interception and continue with the original network request.
   void ResetInterception();
 
   // Override this channel's pending response with a synthesized one. The content will be
   // asynchronously read from the pump.
-  void OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>& aResponseHead, nsInputStreamPump* aPump);
+  void OverrideWithSynthesizedResponse(nsAutoPtr<nsHttpResponseHead>& aResponseHead, nsInputStreamPump* aPump, int64_t aStreamLength);
 
   RequestHeaderTuples mClientSetRequestHeaders;
   nsCOMPtr<nsIChildChannel> mRedirectChannelChild;
   nsRefPtr<InterceptStreamListener> mInterceptListener;
   nsRefPtr<nsInputStreamPump> mSynthesizedResponsePump;
+  int64_t mSynthesizedStreamLength;
 
   bool mIsFromCache;
   bool mCacheEntryAvailable;
   uint32_t     mCacheExpirationTime;
   nsCString    mCachedCharset;
 
   // If ResumeAt is called before AsyncOpen, we need to send extra data upstream
   bool mSendResumeAt;
--- a/netwerk/protocol/http/InterceptedChannel.cpp
+++ b/netwerk/protocol/http/InterceptedChannel.cpp
@@ -97,23 +97,30 @@ InterceptedChannelBase::DoSynthesizeHead
 
 InterceptedChannelChrome::InterceptedChannelChrome(nsHttpChannel* aChannel,
                                                    nsINetworkInterceptController* aController,
                                                    nsICacheEntry* aEntry)
 : InterceptedChannelBase(aController, aChannel->IsNavigation())
 , mChannel(aChannel)
 , mSynthesizedCacheEntry(aEntry)
 {
+  nsresult rv = mChannel->GetApplyConversion(&mOldApplyConversion);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mOldApplyConversion = false;
+  }
 }
 
 void
 InterceptedChannelChrome::NotifyController()
 {
   nsCOMPtr<nsIOutputStream> out;
 
+  // Intercepted responses should already be decoded.
+  mChannel->SetApplyConversion(false);
+
   nsresult rv = mSynthesizedCacheEntry->OpenOutputStream(0, getter_AddRefs(mResponseBody));
   NS_ENSURE_SUCCESS_VOID(rv);
 
   DoNotifyController();
 }
 
 NS_IMETHODIMP
 InterceptedChannelChrome::GetChannel(nsIChannel** aChannel)
@@ -127,16 +134,18 @@ InterceptedChannelChrome::ResetIntercept
 {
   if (!mChannel) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   mSynthesizedCacheEntry->AsyncDoom(nullptr);
   mSynthesizedCacheEntry = nullptr;
 
+  mChannel->SetApplyConversion(mOldApplyConversion);
+
   nsCOMPtr<nsIURI> uri;
   mChannel->GetURI(getter_AddRefs(uri));
 
   nsresult rv = mChannel->StartRedirectChannelToURI(uri, nsIChannelEventSink::REDIRECT_INTERNAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mChannel = nullptr;
   return NS_OK;
@@ -218,16 +227,20 @@ InterceptedChannelChrome::Cancel()
   nsresult rv = mChannel->AsyncAbort(NS_BINDING_ABORTED);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 InterceptedChannelChrome::SetSecurityInfo(nsISupports* aSecurityInfo)
 {
+  if (!mChannel) {
+    return NS_ERROR_FAILURE;
+  }
+
   return mChannel->OverrideSecurityInfo(aSecurityInfo);
 }
 
 InterceptedChannelContent::InterceptedChannelContent(HttpChannelChild* aChannel,
                                                      nsINetworkInterceptController* aController,
                                                      nsIStreamListener* aListener)
 : InterceptedChannelBase(aController, aChannel->IsNavigation())
 , mChannel(aChannel)
@@ -304,17 +317,32 @@ InterceptedChannelContent::FinishSynthes
     return rv;
   }
 
   mResponseBody = nullptr;
 
   rv = mStoragePump->AsyncRead(mStreamListener, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mChannel->OverrideWithSynthesizedResponse(mSynthesizedResponseHead.ref(), mStoragePump);
+  // Intercepted responses should already be decoded.
+  mChannel->SetApplyConversion(false);
+
+  // In our current implementation, the FetchEvent handler will copy the
+  // response stream completely into the pipe backing the input stream so we
+  // can treat the available as the length of the stream.
+  int64_t streamLength;
+  uint64_t available;
+  rv = mSynthesizedInput->Available(&available);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    streamLength = -1;
+  } else {
+    streamLength = int64_t(available);
+  }
+
+  mChannel->OverrideWithSynthesizedResponse(mSynthesizedResponseHead.ref(), mStoragePump, streamLength);
 
   mChannel = nullptr;
   mStreamListener = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 InterceptedChannelContent::Cancel()
@@ -330,13 +358,17 @@ InterceptedChannelContent::Cancel()
   mChannel = nullptr;
   mStreamListener = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 InterceptedChannelContent::SetSecurityInfo(nsISupports* aSecurityInfo)
 {
+  if (!mChannel) {
+    return NS_ERROR_FAILURE;
+  }
+
   return mChannel->OverrideSecurityInfo(aSecurityInfo);
 }
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/InterceptedChannel.h
+++ b/netwerk/protocol/http/InterceptedChannel.h
@@ -61,16 +61,22 @@ public:
 
 class InterceptedChannelChrome : public InterceptedChannelBase
 {
   // The actual channel being intercepted.
   nsRefPtr<nsHttpChannel> mChannel;
 
   // Writeable cache entry for use when synthesizing a response in a parent process
   nsCOMPtr<nsICacheEntry> mSynthesizedCacheEntry;
+
+  // When a channel is intercepted, content decoding is disabled since the
+  // ServiceWorker will have already extracted the decoded data. For parent
+  // process channels we need to preserve the earlier value in case
+  // ResetInterception is called.
+  bool mOldApplyConversion;
 public:
   InterceptedChannelChrome(nsHttpChannel* aChannel,
                            nsINetworkInterceptController* aController,
                            nsICacheEntry* aEntry);
 
   NS_IMETHOD ResetInterception() override;
   NS_IMETHOD FinishSynthesizedResponse() override;
   NS_IMETHOD GetChannel(nsIChannel** aChannel) override;
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -2992,17 +2992,17 @@ nsHttpChannel::OnCacheEntryCheck(nsICach
             }
 
             if (size == 0 && mCacheOnlyMetadata) {
                 // Don't break cache entry load when the entry's data size
                 // is 0 and mCacheOnlyMetadata flag is set. In that case we
                 // want to proceed since the LOAD_ONLY_IF_MODIFIED flag is
                 // also set.
                 MOZ_ASSERT(mLoadFlags & LOAD_ONLY_IF_MODIFIED);
-            } else {
+            } else if (mInterceptCache != INTERCEPTED) {
                 return rv;
             }
         }
     }
 
     bool isHttps = false;
     rv = mURI->SchemeIs("https", &isHttps);
     NS_ENSURE_SUCCESS(rv,rv);
--- a/toolkit/modules/GMPUtils.jsm
+++ b/toolkit/modules/GMPUtils.jsm
@@ -83,17 +83,16 @@ this.GMPPrefs = {
   KEY_CERT_REQUIREBUILTIN:      "media.gmp-manager.cert.requireBuiltIn",
   KEY_UPDATE_LAST_CHECK:        "media.gmp-manager.lastCheck",
   KEY_SECONDS_BETWEEN_CHECKS:   "media.gmp-manager.secondsBetweenChecks",
   KEY_APP_DISTRIBUTION:         "distribution.id",
   KEY_APP_DISTRIBUTION_VERSION: "distribution.version",
   KEY_BUILDID:                  "media.gmp-manager.buildID",
   KEY_CERTS_BRANCH:             "media.gmp-manager.certs.",
   KEY_PROVIDER_ENABLED:         "media.gmp-provider.enabled",
-  KEY_PROVIDER_LASTCHECK:       "media.gmp-manager.lastCheck",
   KEY_LOG_BASE:                 "media.gmp.log.",
   KEY_LOGGING_LEVEL:            this.KEY_LOG_BASE + "level",
   KEY_LOGGING_DUMP:             this.KEY_LOG_BASE + "dump",
 
   /**
    * Obtains the specified preference in relation to the specified plugin.
    * @param aKey The preference key value to use.
    * @param aDefaultValue The default value if no preference exists.
--- a/toolkit/mozapps/extensions/internal/GMPProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/GMPProvider.jsm
@@ -16,21 +16,25 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/GMPUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(
   this, "GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(
+  this, "setTimeout", "resource://gre/modules/Timer.jsm");
 
 const URI_EXTENSION_STRINGS  = "chrome://mozapps/locale/extensions/extensions.properties";
 const STRING_TYPE_NAME       = "type.%ID%.name";
 
 const SEC_IN_A_DAY           = 24 * 60 * 60;
+// How long to wait after a user enabled EME before attempting to download CDMs.
+const GMP_CHECK_DELAY        = 10 * 1000; // milliseconds
 
 const NS_GRE_DIR             = "GreD";
 const CLEARKEY_PLUGIN_ID     = "gmp-clearkey";
 const CLEARKEY_VERSION       = "0.1";
 
 const GMP_LICENSE_INFO       = "gmp_license_info";
 const GMP_LEARN_MORE         = "learn_more_label";
 
@@ -111,16 +115,17 @@ function GMPWrapper(aPluginInfo) {
                         this.onPrefEMEGlobalEnabledChanged, this);
   }
 }
 
 GMPWrapper.prototype = {
   // An active task that checks for plugin updates and installs them.
   _updateTask: null,
   _gmpPath: null,
+  _isUpdateCheckPending: false,
 
   optionsType: AddonManager.OPTIONS_TYPE_INLINE,
   get optionsURL() { return this._plugin.optionsURL; },
 
   set gmpPath(aPath) { this._gmpPath = aPath; },
   get gmpPath() {
     if (!this._gmpPath && this.isInstalled) {
       this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
@@ -234,17 +239,17 @@ GMPWrapper.prototype = {
     if (aReason === AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) {
       if (!AddonManager.shouldAutoUpdate(this)) {
         this._log.trace("findUpdates() - " + this._plugin.id +
                         " - no autoupdate");
         return Promise.resolve(false);
       }
 
       let secSinceLastCheck =
-        Date.now() / 1000 - Preferences.get(GMPPrefs.KEY_PROVIDER_LASTCHECK, 0);
+        Date.now() / 1000 - Preferences.get(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
       if (secSinceLastCheck <= SEC_IN_A_DAY) {
         this._log.trace("findUpdates() - " + this._plugin.id +
                         " - last check was less then a day ago");
         return Promise.resolve(false);
       }
     } else if (aReason !== AddonManager.UPDATE_WHEN_USER_REQUESTED) {
       this._log.trace("findUpdates() - " + this._plugin.id +
                       " - the given reason to update is not supported");
@@ -332,29 +337,40 @@ GMPWrapper.prototype = {
   onPrefEMEGlobalEnabledChanged: function() {
     AddonManagerPrivate.callAddonListeners("onPropertyChanged", this,
                                            ["appDisabled"]);
     if (this.appDisabled) {
       AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
       if (this._gmpPath) {
         this._log.info("onPrefEMEGlobalEnabledChanged() - unregistering gmp " +
                        "directory " + this._gmpPath);
-        gmpService.removePluginDirectory(this._gmpPath);
+        gmpService.removeAndDeletePluginDirectory(this._gmpPath);
       }
+      GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_VERSION, this.id);
       AddonManagerPrivate.callAddonListeners("onUninstalled", this);
     } else {
       AddonManagerPrivate.callInstallListeners("onExternalInstall", null, this,
                                                null, false);
       AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
-      if (this._gmpPath && this.isActive) {
-        this._log.info("onPrefEMEGlobalEnabledChanged() - registering gmp " +
-                       "directory " + this._gmpPath);
-        gmpService.addPluginDirectory(this._gmpPath);
+      AddonManagerPrivate.callAddonListeners("onInstalled", this);
+      if (!this._isUpdateCheckPending) {
+        this._isUpdateCheckPending = true;
+        GMPPrefs.reset(GMPPrefs.KEY_UPDATE_LAST_CHECK, null);
+        // Delay this in case the user changes his mind and doesn't want to
+        // enable EME after all.
+        setTimeout(() => {
+          if (!this.appDisabled) {
+            let gmpInstallManager = new GMPInstallManager();
+            // We don't really care about the results, if someone is interested
+            // they can check the log.
+            gmpInstallManager.simpleCheckAndInstall().then(null, () => {});
+          }
+          this._isUpdateCheckPending = false;
+        }, GMP_CHECK_DELAY);
       }
-      AddonManagerPrivate.callAddonListeners("onInstalled", this);
     }
     if (!this.userDisabled) {
       this._handleEnabledChanged();
     }
   },
 
   onPrefEnabledChanged: function() {
     if (!this._plugin.isEME || !this.appDisabled) {
@@ -362,17 +378,17 @@ GMPWrapper.prototype = {
     }
   },
 
   onPrefVersionChanged: function() {
     AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
     if (this._gmpPath) {
       this._log.info("onPrefVersionChanged() - unregistering gmp directory " +
                      this._gmpPath);
-      gmpService.removePluginDirectory(this._gmpPath);
+      gmpService.removeAndDeletePluginDirectory(this._gmpPath);
     }
     AddonManagerPrivate.callAddonListeners("onUninstalled", this);
 
     AddonManagerPrivate.callInstallListeners("onExternalInstall", null, this,
                                              null, false);
     AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
     this._gmpPath = null;
     if (this.isInstalled) {
--- a/toolkit/mozapps/extensions/test/browser/browser_gmpProvider.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_gmpProvider.js
@@ -95,17 +95,17 @@ add_task(function* initializeState() {
       gPrefs.clearUserPref(getKey(GMPScope.GMPPrefs.KEY_PLUGIN_ENABLED, addon.id));
       gPrefs.clearUserPref(getKey(GMPScope.GMPPrefs.KEY_PLUGIN_LAST_UPDATE, addon.id));
       gPrefs.clearUserPref(getKey(GMPScope.GMPPrefs.KEY_PLUGIN_AUTOUPDATE, addon.id));
       gPrefs.clearUserPref(getKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id));
       gPrefs.clearUserPref(getKey(GMPScope.GMPPrefs.KEY_PLUGIN_FORCEVISIBLE, addon.id));
     }
     gPrefs.clearUserPref(GMPScope.GMPPrefs.KEY_LOGGING_DUMP);
     gPrefs.clearUserPref(GMPScope.GMPPrefs.KEY_LOGGING_LEVEL);
-    gPrefs.clearUserPref(GMPScope.GMPPrefs.KEY_PROVIDER_LASTCHECK);
+    gPrefs.clearUserPref(GMPScope.GMPPrefs.KEY_UPDATE_LAST_CHECK);
     gPrefs.clearUserPref(GMPScope.GMPPrefs.KEY_EME_ENABLED);
     yield GMPScope.GMPProvider.shutdown();
     GMPScope.GMPProvider.startup();
   }));
 
   let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
   gIsEnUsLocale = chrome.getSelectedLocale("global") == "en-US";
 
@@ -319,17 +319,17 @@ add_task(function* testPreferencesButton
       yield deferred.promise;
 
       is(gOptionsObserver.lastDisplayed, addon.id);
     }
   }
 });
 
 add_task(function* testUpdateButton() {
-  gPrefs.clearUserPref(GMPScope.GMPPrefs.KEY_PROVIDER_LASTCHECK);
+  gPrefs.clearUserPref(GMPScope.GMPPrefs.KEY_UPDATE_LAST_CHECK);
 
   let originalInstallManager = GMPScope.GMPInstallManager;
   Object.defineProperty(GMPScope, "GMPInstallManager", {
     value: MockGMPInstallManager,
     writable: true,
     enumerable: true,
     configurable: true
   });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
@@ -226,16 +226,17 @@ add_task(function* test_pluginRegistrati
 
     let addedPaths = [];
     let removedPaths = [];
     let clearPaths = () => { addedPaths = []; removedPaths = []; }
 
     let MockGMPService = {
       addPluginDirectory: path => addedPaths.push(path),
       removePluginDirectory: path => removedPaths.push(path),
+      removeAndDeletePluginDirectory: path => removedPaths.push(path),
     };
 
     GMPScope.gmpService = MockGMPService;
     gPrefs.setBoolPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_ENABLED, addon.id), true);
 
     // Check that the plugin gets registered after startup.
     gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
                       TEST_VERSION);
@@ -302,28 +303,28 @@ add_task(function* test_periodicUpdate()
 
   let addons = yield promiseAddonsByIDs([...gMockAddons.keys()]);
   Assert.equal(addons.length, gMockAddons.size);
 
   for (let addon of addons) {
     gPrefs.clearUserPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_AUTOUPDATE, addon.id));
 
     addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
-    gPrefs.setIntPref(GMPScope.GMPPrefs.KEY_PROVIDER_LASTCHECK, 0);
+    gPrefs.setIntPref(GMPScope.GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
     let result =
       yield addon.findUpdates({}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
     Assert.strictEqual(result, false);
 
     addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE;
-    gPrefs.setIntPref(GMPScope.GMPPrefs.KEY_PROVIDER_LASTCHECK, Date.now() / 1000 - 60);
+    gPrefs.setIntPref(GMPScope.GMPPrefs.KEY_UPDATE_LAST_CHECK, Date.now() / 1000 - 60);
     result =
       yield addon.findUpdates({}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
     Assert.strictEqual(result, false);
 
-    gPrefs.setIntPref(GMPScope.GMPPrefs.KEY_PROVIDER_LASTCHECK,
+    gPrefs.setIntPref(GMPScope.GMPPrefs.KEY_UPDATE_LAST_CHECK,
                      Date.now() / 1000 - 2 * GMPScope.SEC_IN_A_DAY);
     gInstalledAddonId = "";
     result =
       yield addon.findUpdates({}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
     Assert.strictEqual(result, true);
     Assert.equal(gInstalledAddonId, addon.id);
   }