Bug 940273 - Part 4 - Initial implementation of Service Worker Cache. r=ehsan,baku,janv
authorBen Kelly <ben@wanderview.com>
Mon, 02 Mar 2015 14:20:00 +0100
changeset 231556 b8dc4a7b444aeb344121fb29ac6575e241899d00
parent 231555 cdfddaf5a26dfad571d42adf528486b2c0cab728
child 231557 0b2f7b91f7f2c0b6bf34ab763ba2aadc5fc189b4
push id28355
push userkwierso@gmail.com
push dateWed, 04 Mar 2015 00:49:07 +0000
treeherdermozilla-central@f42b9946f08f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, baku, janv
bugs940273
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
Bug 940273 - Part 4 - Initial implementation of Service Worker Cache. r=ehsan,baku,janv
dom/bindings/Bindings.conf
dom/bindings/Errors.msg
dom/cache/Action.cpp
dom/cache/Action.h
dom/cache/ActorChild.cpp
dom/cache/ActorChild.h
dom/cache/ActorUtils.h
dom/cache/AutoUtils.cpp
dom/cache/AutoUtils.h
dom/cache/Cache.cpp
dom/cache/Cache.h
dom/cache/CacheChild.cpp
dom/cache/CacheChild.h
dom/cache/CacheInitData.ipdlh
dom/cache/CacheParent.cpp
dom/cache/CacheParent.h
dom/cache/CacheStorage.cpp
dom/cache/CacheStorage.h
dom/cache/CacheStorageChild.cpp
dom/cache/CacheStorageChild.h
dom/cache/CacheStorageParent.cpp
dom/cache/CacheStorageParent.h
dom/cache/CacheStreamControlChild.cpp
dom/cache/CacheStreamControlChild.h
dom/cache/CacheStreamControlParent.cpp
dom/cache/CacheStreamControlParent.h
dom/cache/Context.cpp
dom/cache/Context.h
dom/cache/DBAction.cpp
dom/cache/DBAction.h
dom/cache/DBSchema.cpp
dom/cache/DBSchema.h
dom/cache/Feature.cpp
dom/cache/Feature.h
dom/cache/FetchPut.cpp
dom/cache/FetchPut.h
dom/cache/FileUtils.cpp
dom/cache/FileUtils.h
dom/cache/IPCUtils.h
dom/cache/Manager.cpp
dom/cache/Manager.h
dom/cache/ManagerId.cpp
dom/cache/ManagerId.h
dom/cache/PCache.ipdl
dom/cache/PCacheStorage.ipdl
dom/cache/PCacheStreamControl.ipdl
dom/cache/PCacheTypes.ipdlh
dom/cache/PrincipalVerifier.cpp
dom/cache/PrincipalVerifier.h
dom/cache/QuotaClient.cpp
dom/cache/QuotaClient.h
dom/cache/ReadStream.cpp
dom/cache/ReadStream.h
dom/cache/SavedTypes.h
dom/cache/StreamList.cpp
dom/cache/StreamList.h
dom/cache/StreamUtils.cpp
dom/cache/StreamUtils.h
dom/cache/TypeUtils.cpp
dom/cache/TypeUtils.h
dom/cache/Types.h
dom/cache/moz.build
dom/indexedDB/PBackgroundIDBVersionChangeTransaction.ipdl
dom/moz.build
dom/quota/Client.h
dom/quota/QuotaManager.cpp
ipc/glue/BackgroundChildImpl.cpp
ipc/glue/BackgroundChildImpl.h
ipc/glue/BackgroundParentImpl.cpp
ipc/glue/BackgroundParentImpl.h
ipc/glue/PBackground.ipdl
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -171,16 +171,24 @@ DOMInterfaces = {
     'nativeType':
       'mozilla::dom::bluetooth::BluetoothPairingListener',
 },
 
 'BoxObject': {
     'resultNotAddRefed': ['element'],
 },
 
+'Cache': {
+    'nativeType': 'mozilla::dom::cache::Cache',
+},
+
+'CacheStorage': {
+    'nativeType': 'mozilla::dom::cache::CacheStorage',
+},
+
 'CameraCapabilities': {
     'nativeType': 'mozilla::dom::CameraCapabilities',
     'headerFile': 'DOMCameraCapabilities.h'
 },
 
 'CameraControl': {
     'nativeType': 'mozilla::nsDOMCameraControl',
     'headerFile': 'DOMCameraControl.h',
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -66,9 +66,10 @@ MSG_DEF(MSG_FETCH_BODY_CONSUMED_ERROR, 0
 MSG_DEF(MSG_RESPONSE_INVALID_STATUSTEXT_ERROR, 0, JSEXN_TYPEERR, "Response statusText may not contain newline or carriage return.")
 MSG_DEF(MSG_FETCH_FAILED, 0, JSEXN_TYPEERR, "NetworkError when attempting to fetch resource.")
 MSG_DEF(MSG_NO_BODY_ALLOWED_FOR_GET_AND_HEAD, 0, JSEXN_TYPEERR, "HEAD or GET Request cannot have a body.")
 MSG_DEF(MSG_DEFINE_NON_CONFIGURABLE_PROP_ON_WINDOW, 0, JSEXN_TYPEERR, "Not allowed to define a non-configurable property on the WindowProxy object")
 MSG_DEF(MSG_INVALID_ZOOMANDPAN_VALUE_ERROR, 0, JSEXN_RANGEERR, "Invalid zoom and pan value.")
 MSG_DEF(MSG_INVALID_TRANSFORM_ANGLE_ERROR, 0, JSEXN_RANGEERR, "Invalid transform angle.")
 MSG_DEF(MSG_INVALID_RESPONSE_STATUSCODE_ERROR, 0, JSEXN_RANGEERR, "Invalid response status code.")
 MSG_DEF(MSG_INVALID_REDIRECT_STATUSCODE_ERROR, 0, JSEXN_RANGEERR, "Invalid redirect status code.")
+MSG_DEF(MSG_INVALID_URL_SCHEME, 2, JSEXN_TYPEERR, "{0} URL {1} must be either http:// or https://.")
 MSG_DEF(MSG_RESPONSE_URL_IS_NULL, 0, JSEXN_TYPEERR, "Cannot set Response.finalURL when Response.url is null.")
new file mode 100644
--- /dev/null
+++ b/dom/cache/Action.cpp
@@ -0,0 +1,40 @@
+/* -*- 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/Action.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+NS_IMPL_ISUPPORTS0(mozilla::dom::cache::Action::Resolver);
+
+void
+Action::CancelOnInitiatingThread()
+{
+  NS_ASSERT_OWNINGTHREAD(Action);
+  MOZ_ASSERT(!mCanceled);
+  mCanceled = true;
+}
+
+Action::Action()
+  : mCanceled(false)
+{
+}
+
+Action::~Action()
+{
+}
+
+bool
+Action::IsCanceled() const
+{
+  return mCanceled;
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/Action.h
@@ -0,0 +1,88 @@
+/* -*- 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_Action_h
+#define mozilla_dom_cache_Action_h
+
+#include "mozilla/Atomics.h"
+#include "mozilla/dom/cache/Types.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class Action
+{
+public:
+  class Resolver : public nsISupports
+  {
+  protected:
+    // virtual because deleted through base class pointer
+    virtual ~Resolver() { }
+
+  public:
+    // Note: Action must drop Resolver ref after calling Resolve()!
+    // Note: Must be called on the same thread used to execute
+    //       Action::RunOnTarget().
+    virtual void Resolve(nsresult aRv) = 0;
+
+    // We must use ISUPPORTS for our refcounting here because sub-classes also
+    // want to inherit interfaces like nsIRunnable.
+    NS_DECL_THREADSAFE_ISUPPORTS
+  };
+
+  // Execute operations on the target thread.  Once complete call
+  // Resolver::Resolve().  This can be done sync or async.
+  // Note: Action should hold Resolver ref until its ready to call Resolve().
+  // Note: The "target" thread is determined when the Action is scheduled on
+  //       Context.  The Action should not assume any particular thread is used.
+  virtual void RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo) = 0;
+
+  // Called on initiating thread when the Action is canceled.  The Action is
+  // responsible for calling Resolver::Resolve() as normal; either with a
+  // normal error code or NS_ERROR_ABORT.  If CancelOnInitiatingThread() is
+  // called after Resolve() has already occurred, then the cancel can be
+  // ignored.
+  //
+  // Cancellation is a best effort to stop processing as soon as possible, but
+  // does not guarantee the Action will not run.
+  //
+  // Default implementation sets an internal cancellation flag that can be
+  // queried with IsCanceled().
+  virtual void CancelOnInitiatingThread();
+
+  // Executed on the initiating thread and is passed the nsresult given to
+  // Resolver::Resolve().
+  virtual void CompleteOnInitiatingThread(nsresult aRv) { }
+
+  // Executed on the initiating thread.  If this Action will operate on the
+  // given cache ID then override this to return true.
+  virtual bool MatchesCacheId(CacheId aCacheId) const { return false; }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(cache::Action)
+  NS_DECL_OWNINGTHREAD
+
+protected:
+  Action();
+
+  // virtual because deleted through base class pointer
+  virtual ~Action();
+
+  // Check if this Action has been canceled.  May be called from any thread,
+  // but typically used from the target thread.
+  bool IsCanceled() const;
+
+private:
+  // Accessible from any thread.
+  Atomic<bool> mCanceled;
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_Action_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/ActorChild.cpp
@@ -0,0 +1,53 @@
+/* -*- 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/ActorChild.h"
+
+#include "mozilla/dom/cache/Feature.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+void
+ActorChild::SetFeature(Feature* aFeature)
+{
+  MOZ_ASSERT(!mFeature);
+  mFeature = aFeature;
+  if (mFeature) {
+    mFeature->AddActor(this);
+  }
+}
+
+void
+ActorChild::RemoveFeature()
+{
+  if (mFeature) {
+    mFeature->RemoveActor(this);
+    mFeature = nullptr;
+  }
+}
+
+Feature*
+ActorChild::GetFeature() const
+{
+  return mFeature;
+}
+
+bool
+ActorChild::FeatureNotified() const
+{
+  return mFeature && mFeature->Notified();
+}
+
+ActorChild::~ActorChild()
+{
+  MOZ_ASSERT(!mFeature);
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/ActorChild.h
@@ -0,0 +1,47 @@
+/* -*- 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_ActioChild_h
+#define mozilla_dom_cache_ActioChild_h
+
+#include "nsRefPtr.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class Feature;
+
+class ActorChild
+{
+public:
+  virtual void
+  StartDestroy() = 0;
+
+  void
+  SetFeature(Feature* aFeature);
+
+  void
+  RemoveFeature();
+
+  Feature*
+  GetFeature() const;
+
+  bool
+  FeatureNotified() const;
+
+protected:
+  ~ActorChild();
+
+private:
+  nsRefPtr<Feature> mFeature;
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_ActioChild_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/ActorUtils.h
@@ -0,0 +1,65 @@
+/* -*- 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_ActorUtils_h
+#define mozilla_dom_cache_ActorUtils_h
+
+#include "mozilla/dom/cache/Types.h"
+
+namespace mozilla {
+
+namespace ipc {
+class PBackgroundParent;
+class PrincipalInfo;
+}
+
+namespace dom {
+namespace cache {
+
+class PCacheChild;
+class PCacheParent;
+class PCacheStreamControlChild;
+class PCacheStreamControlParent;
+class PCacheStorageChild;
+class PCacheStorageParent;
+
+// Factory methods for use in ipc/glue methods.  Implemented in individual actor
+// cpp files.
+
+PCacheChild*
+AllocPCacheChild();
+
+void
+DeallocPCacheChild(PCacheChild* aActor);
+
+void
+DeallocPCacheParent(PCacheParent* aActor);
+
+PCacheStreamControlChild*
+AllocPCacheStreamControlChild();
+
+void
+DeallocPCacheStreamControlChild(PCacheStreamControlChild* aActor);
+
+void
+DeallocPCacheStreamControlParent(PCacheStreamControlParent* aActor);
+
+PCacheStorageParent*
+AllocPCacheStorageParent(mozilla::ipc::PBackgroundParent* aManagingActor,
+                         Namespace aNamespace,
+                         const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
+
+void
+DeallocPCacheStorageChild(PCacheStorageChild* aActor);
+
+void
+DeallocPCacheStorageParent(PCacheStorageParent* aActor);
+
+} // namesapce cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_ActorUtils_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/AutoUtils.cpp
@@ -0,0 +1,438 @@
+/* -*- 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/AutoUtils.h"
+
+#include "mozilla/unused.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::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
+};
+
+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) {
+    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)
+{
+  if (aReadStreamOrVoid.type() == PCacheReadStreamOrVoid::Tvoid_t) {
+    return;
+  }
+
+  CleanupChildFds(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) {
+    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
+CleanupParentFds(PCacheReadStreamOrVoid& aReadStreamOrVoid, CleanupAction aAction)
+{
+  if (aReadStreamOrVoid.type() == PCacheReadStreamOrVoid::Tvoid_t) {
+    return;
+  }
+
+  CleanupParentFds(aReadStreamOrVoid.get_PCacheReadStream(), aAction);
+}
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+using mozilla::ipc::PBackgroundParent;
+
+AutoChildBase::AutoChildBase(TypeUtils* aTypeUtils)
+  : mTypeUtils(aTypeUtils)
+  , mSent(false)
+{
+  MOZ_ASSERT(mTypeUtils);
+}
+
+AutoChildBase::~AutoChildBase()
+{
+}
+
+// --------------------------------------------
+
+AutoChildRequest::AutoChildRequest(TypeUtils* aTypeUtils)
+  : AutoChildBase(aTypeUtils)
+{
+  mRequestOrVoid = void_t();
+}
+
+AutoChildRequest::~AutoChildRequest()
+{
+  if (mRequestOrVoid.type() != PCacheRequestOrVoid::TPCacheRequest) {
+    return;
+  }
+
+  CleanupAction action = mSent ? ForgetFds : DeleteFds;
+  CleanupChildFds(mRequestOrVoid.get_PCacheRequest().body(), action);
+}
+
+void
+AutoChildRequest::Add(InternalRequest* aRequest, BodyAction aBodyAction,
+                      ReferrerAction aReferrerAction, SchemeAction aSchemeAction,
+                      ErrorResult& aRv)
+{
+  MOZ_ASSERT(!mSent);
+  MOZ_ASSERT(mRequestOrVoid.type() == PCacheRequestOrVoid::Tvoid_t);
+  mRequestOrVoid = PCacheRequest();
+  mTypeUtils->ToPCacheRequest(mRequestOrVoid.get_PCacheRequest(), aRequest,
+                              aBodyAction, aReferrerAction, aSchemeAction, aRv);
+}
+
+const PCacheRequest&
+AutoChildRequest::SendAsRequest()
+{
+  MOZ_ASSERT(mRequestOrVoid.type() == PCacheRequestOrVoid::TPCacheRequest);
+  return mRequestOrVoid.get_PCacheRequest();
+}
+
+const PCacheRequestOrVoid&
+AutoChildRequest::SendAsRequestOrVoid()
+{
+  return mRequestOrVoid;
+}
+
+// --------------------------------------------
+
+AutoChildRequestList::AutoChildRequestList(TypeUtils* aTypeUtils,
+                                           uint32_t aCapacity)
+  : AutoChildBase(aTypeUtils)
+{
+  mRequestList.SetCapacity(aCapacity);
+}
+
+AutoChildRequestList::~AutoChildRequestList()
+{
+  CleanupAction action = mSent ? ForgetFds : DeleteFds;
+  for (uint32_t i = 0; i < mRequestList.Length(); ++i) {
+    CleanupChildFds(mRequestList[i].body(), action);
+  }
+}
+
+void
+AutoChildRequestList::Add(InternalRequest* aRequest, BodyAction aBodyAction,
+                          ReferrerAction aReferrerAction,
+                          SchemeAction aSchemeAction, ErrorResult& aRv)
+{
+  MOZ_ASSERT(!mSent);
+
+  // The FileDescriptorSetChild asserts in its destructor that all fds have
+  // been removed.  The copy constructor, however, simply duplicates the
+  // fds without removing any.  This means each temporary and copy must be
+  // explicitly cleaned up.
+  //
+  // Avoid a lot of this hassle by making sure we only create one here.  On
+  // error we remove it.
+
+  PCacheRequest* request = mRequestList.AppendElement();
+  mTypeUtils->ToPCacheRequest(*request, aRequest, aBodyAction, aReferrerAction,
+                              aSchemeAction, aRv);
+  if (aRv.Failed()) {
+    mRequestList.RemoveElementAt(mRequestList.Length() - 1);
+  }
+}
+
+const nsTArray<PCacheRequest>&
+AutoChildRequestList::SendAsRequestList()
+{
+  MOZ_ASSERT(!mSent);
+  mSent = true;
+  return mRequestList;
+}
+
+// --------------------------------------------
+
+AutoChildRequestResponse::AutoChildRequestResponse(TypeUtils* aTypeUtils)
+  : AutoChildBase(aTypeUtils)
+{
+  // 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);
+}
+
+void
+AutoChildRequestResponse::Add(InternalRequest* aRequest, BodyAction aBodyAction,
+                              ReferrerAction aReferrerAction,
+                              SchemeAction aSchemeAction, ErrorResult& aRv)
+{
+  MOZ_ASSERT(!mSent);
+  mTypeUtils->ToPCacheRequest(mRequestResponse.request(), aRequest, aBodyAction,
+                              aReferrerAction, aSchemeAction, aRv);
+}
+
+void
+AutoChildRequestResponse::Add(Response& aResponse, ErrorResult& aRv)
+{
+  MOZ_ASSERT(!mSent);
+  mTypeUtils->ToPCacheResponse(mRequestResponse.response(), aResponse, aRv);
+}
+
+const CacheRequestResponse&
+AutoChildRequestResponse::SendAsRequestResponse()
+{
+  MOZ_ASSERT(!mSent);
+  mSent = true;
+  return mRequestResponse;
+}
+
+// --------------------------------------------
+
+AutoParentBase::AutoParentBase(PBackgroundParent* aManager)
+  : mManager(aManager)
+  , mStreamControl(nullptr)
+  , mSent(false)
+{
+  MOZ_ASSERT(mManager);
+}
+
+AutoParentBase::~AutoParentBase()
+{
+  if (!mSent && mStreamControl) {
+    unused << PCacheStreamControlParent::Send__delete__(mStreamControl);
+  }
+}
+
+void
+AutoParentBase::SerializeReadStream(const nsID& aId, StreamList* aStreamList,
+                                    PCacheReadStream* aReadStreamOut)
+{
+  MOZ_ASSERT(aStreamList);
+  MOZ_ASSERT(aReadStreamOut);
+  MOZ_ASSERT(!mSent);
+
+  nsCOMPtr<nsIInputStream> stream = aStreamList->Extract(aId);
+  MOZ_ASSERT(stream);
+
+  if (!mStreamControl) {
+    mStreamControl = static_cast<CacheStreamControlParent*>(
+      mManager->SendPCacheStreamControlConstructor(new CacheStreamControlParent()));
+
+    // If this failed, then the child process is gone.  Warn and allow actor
+    // cleanup to proceed as normal.
+    if (!mStreamControl) {
+      NS_WARNING("Cache failed to create stream control actor.");
+      return;
+    }
+  }
+
+  aStreamList->SetStreamControl(mStreamControl);
+
+  nsRefPtr<ReadStream> readStream = ReadStream::Create(mStreamControl,
+                                                       aId, stream);
+  readStream->Serialize(aReadStreamOut);
+}
+
+// --------------------------------------------
+
+AutoParentRequestList::AutoParentRequestList(PBackgroundParent* aManager,
+                                             uint32_t aCapacity)
+  : AutoParentBase(aManager)
+{
+  mRequestList.SetCapacity(aCapacity);
+}
+
+AutoParentRequestList::~AutoParentRequestList()
+{
+  CleanupAction action = mSent ? ForgetFds : DeleteFds;
+  for (uint32_t i = 0; i < mRequestList.Length(); ++i) {
+    CleanupParentFds(mRequestList[i].body(), action);
+  }
+}
+
+void
+AutoParentRequestList::Add(const SavedRequest& aSavedRequest,
+                           StreamList* aStreamList)
+{
+  MOZ_ASSERT(!mSent);
+
+  mRequestList.AppendElement(aSavedRequest.mValue);
+  PCacheRequest& request = mRequestList.LastElement();
+
+  if (!aSavedRequest.mHasBodyId) {
+    request.body() = void_t();
+    return;
+  }
+
+  request.body() = PCacheReadStream();
+  SerializeReadStream(aSavedRequest.mBodyId, aStreamList,
+                      &request.body().get_PCacheReadStream());
+}
+
+const nsTArray<PCacheRequest>&
+AutoParentRequestList::SendAsRequestList()
+{
+  MOZ_ASSERT(!mSent);
+  mSent = true;
+  return mRequestList;
+}
+
+// --------------------------------------------
+
+AutoParentResponseList::AutoParentResponseList(PBackgroundParent* aManager,
+                                               uint32_t aCapacity)
+  : AutoParentBase(aManager)
+{
+  mResponseList.SetCapacity(aCapacity);
+}
+
+AutoParentResponseList::~AutoParentResponseList()
+{
+  CleanupAction action = mSent ? ForgetFds : DeleteFds;
+  for (uint32_t i = 0; i < mResponseList.Length(); ++i) {
+    CleanupParentFds(mResponseList[i].body(), action);
+  }
+}
+
+void
+AutoParentResponseList::Add(const SavedResponse& aSavedResponse,
+                            StreamList* aStreamList)
+{
+  MOZ_ASSERT(!mSent);
+
+  mResponseList.AppendElement(aSavedResponse.mValue);
+  PCacheResponse& response = mResponseList.LastElement();
+
+  if (!aSavedResponse.mHasBodyId) {
+    response.body() = void_t();
+    return;
+  }
+
+  response.body() = PCacheReadStream();
+  SerializeReadStream(aSavedResponse.mBodyId, aStreamList,
+                      &response.body().get_PCacheReadStream());
+}
+
+const nsTArray<PCacheResponse>&
+AutoParentResponseList::SendAsResponseList()
+{
+  MOZ_ASSERT(!mSent);
+  mSent = true;
+  return mResponseList;
+}
+
+// --------------------------------------------
+
+AutoParentResponseOrVoid::AutoParentResponseOrVoid(ipc::PBackgroundParent* aManager)
+  : AutoParentBase(aManager)
+{
+  mResponseOrVoid = void_t();
+}
+
+AutoParentResponseOrVoid::~AutoParentResponseOrVoid()
+{
+  if (mResponseOrVoid.type() != PCacheResponseOrVoid::TPCacheResponse) {
+    return;
+  }
+
+  CleanupAction action = mSent ? ForgetFds : DeleteFds;
+  CleanupParentFds(mResponseOrVoid.get_PCacheResponse().body(), action);
+}
+
+void
+AutoParentResponseOrVoid::Add(const SavedResponse& aSavedResponse,
+                              StreamList* aStreamList)
+{
+  MOZ_ASSERT(!mSent);
+
+  mResponseOrVoid = aSavedResponse.mValue;
+  PCacheResponse& response = mResponseOrVoid.get_PCacheResponse();
+
+  if (!aSavedResponse.mHasBodyId) {
+    response.body() = void_t();
+    return;
+  }
+
+  response.body() = PCacheReadStream();
+  SerializeReadStream(aSavedResponse.mBodyId, aStreamList,
+                      &response.body().get_PCacheReadStream());
+}
+
+const PCacheResponseOrVoid&
+AutoParentResponseOrVoid::SendAsResponseOrVoid()
+{
+  MOZ_ASSERT(!mSent);
+  mSent = true;
+  return mResponseOrVoid;
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/AutoUtils.h
@@ -0,0 +1,176 @@
+/* -*- 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_AutoUtils_h
+#define mozilla_dom_cache_AutoUtils_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/cache/PCacheTypes.h"
+#include "mozilla/dom/cache/TypeUtils.h"
+#include "nsTArray.h"
+
+struct nsID;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace ipc {
+class PBackgroundParent;
+}
+
+namespace dom {
+
+class InternalRequest;
+class OwningRequestOrUSVString;
+class RequestOrUSVString;
+template<typename T> class Optional;
+
+namespace cache {
+
+class CacheStreamControlParent;
+struct SavedRequest;
+struct SavedResponse;
+class StreamList;
+
+// A collection of RAII-style helper classes to ensure that IPC
+// FileDescriptorSet actors are properly cleaned up.  The user of these actors
+// must manually either Forget() the Fds or Send__delete__() the actor
+// depending on if the descriptors were actually sent.
+//
+// Note, these should only be used when *sending* streams across IPC.  The
+// deserialization case is handled by creating a ReadStream object.
+
+class MOZ_STACK_CLASS AutoChildBase
+{
+protected:
+  typedef TypeUtils::BodyAction BodyAction;
+  typedef TypeUtils::ReferrerAction ReferrerAction;
+  typedef TypeUtils::SchemeAction SchemeAction;
+
+  AutoChildBase(TypeUtils* aTypeUtils);
+  virtual ~AutoChildBase() = 0;
+
+  TypeUtils* mTypeUtils;
+  bool mSent;
+};
+
+class MOZ_STACK_CLASS AutoChildRequest MOZ_FINAL : public AutoChildBase
+{
+public:
+  explicit AutoChildRequest(TypeUtils* aTypeUtils);
+  ~AutoChildRequest();
+
+  void Add(InternalRequest* aRequest, BodyAction aBodyAction,
+           ReferrerAction aReferrerAction, SchemeAction aSchemeAction,
+           ErrorResult& aRv);
+
+  const PCacheRequest& SendAsRequest();
+  const PCacheRequestOrVoid& SendAsRequestOrVoid();
+
+private:
+  PCacheRequestOrVoid mRequestOrVoid;
+};
+
+class MOZ_STACK_CLASS AutoChildRequestList MOZ_FINAL : public AutoChildBase
+{
+public:
+  AutoChildRequestList(TypeUtils* aTypeUtils, uint32_t aCapacity);
+  ~AutoChildRequestList();
+
+  void Add(InternalRequest* aRequest, BodyAction aBodyAction,
+           ReferrerAction aReferrerAction, SchemeAction aSchemeAction,
+           ErrorResult& aRv);
+
+  const nsTArray<PCacheRequest>& SendAsRequestList();
+
+private:
+  // Allocates ~5k inline in the stack-only class
+  nsAutoTArray<PCacheRequest, 32> mRequestList;
+};
+
+class MOZ_STACK_CLASS AutoChildRequestResponse MOZ_FINAL : public AutoChildBase
+{
+public:
+  explicit AutoChildRequestResponse(TypeUtils* aTypeUtils);
+  ~AutoChildRequestResponse();
+
+  void Add(InternalRequest* aRequest, BodyAction aBodyAction,
+           ReferrerAction aReferrerAction, SchemeAction aSchemeAction,
+           ErrorResult& aRv);
+  void Add(Response& aResponse, ErrorResult& aRv);
+
+  const CacheRequestResponse& SendAsRequestResponse();
+
+private:
+  CacheRequestResponse mRequestResponse;
+};
+
+class MOZ_STACK_CLASS AutoParentBase
+{
+protected:
+  explicit AutoParentBase(mozilla::ipc::PBackgroundParent* aManager);
+  virtual ~AutoParentBase() = 0;
+
+  void SerializeReadStream(const nsID& aId, StreamList* aStreamList,
+                           PCacheReadStream* aReadStreamOut);
+
+  mozilla::ipc::PBackgroundParent* mManager;
+  CacheStreamControlParent* mStreamControl;
+  bool mSent;
+};
+
+class MOZ_STACK_CLASS AutoParentRequestList MOZ_FINAL : public AutoParentBase
+{
+public:
+  AutoParentRequestList(mozilla::ipc::PBackgroundParent* aManager,
+                        uint32_t aCapacity);
+  ~AutoParentRequestList();
+
+  void Add(const SavedRequest& aSavedRequest, StreamList* aStreamList);
+
+  const nsTArray<PCacheRequest>& SendAsRequestList();
+
+private:
+  // Allocates ~5k inline in the stack-only class
+  nsAutoTArray<PCacheRequest, 32> mRequestList;
+};
+
+class MOZ_STACK_CLASS AutoParentResponseList MOZ_FINAL : public AutoParentBase
+{
+public:
+  AutoParentResponseList(mozilla::ipc::PBackgroundParent* aManager,
+                         uint32_t aCapacity);
+  ~AutoParentResponseList();
+
+  void Add(const SavedResponse& aSavedResponse, StreamList* aStreamList);
+
+  const nsTArray<PCacheResponse>& SendAsResponseList();
+
+private:
+  // Allocates ~4k inline in the stack-only class
+  nsAutoTArray<PCacheResponse, 32> mResponseList;
+};
+
+class MOZ_STACK_CLASS AutoParentResponseOrVoid MOZ_FINAL : public AutoParentBase
+{
+public:
+  explicit AutoParentResponseOrVoid(mozilla::ipc::PBackgroundParent* aManager);
+  ~AutoParentResponseOrVoid();
+
+  void Add(const SavedResponse& aSavedResponse, StreamList* aStreamList);
+
+  const PCacheResponseOrVoid& SendAsResponseOrVoid();
+
+private:
+  PCacheResponseOrVoid mResponseOrVoid;
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_AutoUtils_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/Cache.cpp
@@ -0,0 +1,586 @@
+/* -*- 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/Cache.h"
+
+#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/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"
+
+namespace {
+
+using mozilla::ErrorResult;
+using mozilla::dom::MSG_INVALID_REQUEST_METHOD;
+using mozilla::dom::OwningRequestOrUSVString;
+using mozilla::dom::Request;
+using mozilla::dom::RequestOrUSVString;
+
+static bool
+IsValidPutRequestMethod(const Request& aRequest, ErrorResult& aRv)
+{
+  nsAutoCString method;
+  aRequest.GetMethod(method);
+  bool valid = method.LowerCaseEqualsLiteral("get");
+  if (!valid) {
+    NS_ConvertASCIItoUTF16 label(method);
+    aRv.ThrowTypeError(MSG_INVALID_REQUEST_METHOD, &label);
+  }
+  return valid;
+}
+
+static bool
+IsValidPutRequestMethod(const RequestOrUSVString& aRequest,
+                        ErrorResult& aRv)
+{
+  // If the provided request is a string URL, then it will default to
+  // a valid http method automatically.
+  if (!aRequest.IsRequest()) {
+    return true;
+  }
+  return IsValidPutRequestMethod(aRequest.GetAsRequest(), aRv);
+}
+
+static bool
+IsValidPutRequestMethod(const OwningRequestOrUSVString& aRequest,
+                        ErrorResult& aRv)
+{
+  if (!aRequest.IsRequest()) {
+    return true;
+  }
+  return IsValidPutRequestMethod(*aRequest.GetAsRequest().get(), aRv);
+}
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+using mozilla::ErrorResult;
+using mozilla::unused;
+using mozilla::dom::workers::GetCurrentThreadWorkerPrivate;
+using mozilla::dom::workers::WorkerPrivate;
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::cache::Cache);
+NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::cache::Cache);
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Cache, mGlobal, mRequestPromises)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Cache)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END
+
+Cache::Cache(nsIGlobalObject* aGlobal, CacheChild* aActor)
+  : mGlobal(aGlobal)
+  , mActor(aActor)
+{
+  MOZ_ASSERT(mGlobal);
+  MOZ_ASSERT(mActor);
+  mActor->SetListener(this);
+}
+
+already_AddRefed<Promise>
+Cache::Match(const RequestOrUSVString& aRequest,
+             const CacheQueryOptions& aOptions, ErrorResult& aRv)
+{
+  MOZ_ASSERT(mActor);
+
+  nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
+  if (!promise) {
+    return nullptr;
+  }
+
+  nsRefPtr<InternalRequest> ir = ToInternalRequest(aRequest, IgnoreBody, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  AutoChildRequest request(this);
+
+  request.Add(ir, IgnoreBody, PassThroughReferrer, IgnoreInvalidScheme, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  PCacheQueryParams params;
+  ToPCacheQueryParams(params, aOptions);
+
+  RequestId requestId = AddRequestPromise(promise, aRv);
+
+  unused << mActor->SendMatch(requestId, request.SendAsRequest(), params);
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+Cache::MatchAll(const Optional<RequestOrUSVString>& aRequest,
+                const CacheQueryOptions& aOptions, ErrorResult& aRv)
+{
+  MOZ_ASSERT(mActor);
+
+  nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
+  if (!promise) {
+    return nullptr;
+  }
+
+  AutoChildRequest request(this);
+
+  if (aRequest.WasPassed()) {
+    nsRefPtr<InternalRequest> ir = ToInternalRequest(aRequest.Value(),
+                                                     IgnoreBody, aRv);
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+
+    request.Add(ir, IgnoreBody, PassThroughReferrer, IgnoreInvalidScheme, aRv);
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+  }
+
+  PCacheQueryParams params;
+  ToPCacheQueryParams(params, aOptions);
+
+  RequestId requestId = AddRequestPromise(promise, aRv);
+
+  unused << mActor->SendMatchAll(requestId, request.SendAsRequestOrVoid(),
+                                 params);
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+Cache::Add(const RequestOrUSVString& aRequest, ErrorResult& aRv)
+{
+  MOZ_ASSERT(mActor);
+
+  if (!IsValidPutRequestMethod(aRequest, aRv)) {
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
+  if (!promise) {
+    return nullptr;
+  }
+
+  nsRefPtr<InternalRequest> ir = ToInternalRequest(aRequest, ReadBody, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  AutoChildRequestList requests(this, 1);
+  requests.Add(ir, ReadBody, ExpandReferrer, NetworkErrorOnInvalidScheme, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  RequestId requestId = AddRequestPromise(promise, aRv);
+
+  unused << mActor->SendAddAll(requestId, requests.SendAsRequestList());
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+Cache::AddAll(const Sequence<OwningRequestOrUSVString>& aRequests,
+              ErrorResult& aRv)
+{
+  MOZ_ASSERT(mActor);
+
+  nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
+  if (!promise) {
+    return nullptr;
+  }
+
+  // If there is no work to do, then resolve immediately
+  if (aRequests.IsEmpty()) {
+    promise->MaybeResolve(JS::UndefinedHandleValue);
+    return promise.forget();
+  }
+
+  AutoChildRequestList requests(this, aRequests.Length());
+
+  for (uint32_t i = 0; i < aRequests.Length(); ++i) {
+    if (!IsValidPutRequestMethod(aRequests[i], aRv)) {
+      return nullptr;
+    }
+
+    nsRefPtr<InternalRequest> ir = ToInternalRequest(aRequests[i], ReadBody,
+                                                     aRv);
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+
+    requests.Add(ir, ReadBody, ExpandReferrer, NetworkErrorOnInvalidScheme,
+                 aRv);
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+  }
+
+  RequestId requestId = AddRequestPromise(promise, aRv);
+
+  unused << mActor->SendAddAll(requestId, requests.SendAsRequestList());
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+Cache::Put(const RequestOrUSVString& aRequest, Response& aResponse,
+           ErrorResult& aRv)
+{
+  MOZ_ASSERT(mActor);
+
+  if (!IsValidPutRequestMethod(aRequest, aRv)) {
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
+  if (!promise) {
+    return nullptr;
+  }
+
+  nsRefPtr<InternalRequest> ir = ToInternalRequest(aRequest, ReadBody, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  AutoChildRequestResponse put(this);
+  put.Add(ir, ReadBody, PassThroughReferrer, TypeErrorOnInvalidScheme, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  put.Add(aResponse, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  RequestId requestId = AddRequestPromise(promise, aRv);
+
+  unused << mActor->SendPut(requestId, put.SendAsRequestResponse());
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+Cache::Delete(const RequestOrUSVString& aRequest,
+              const CacheQueryOptions& aOptions, ErrorResult& aRv)
+{
+  MOZ_ASSERT(mActor);
+
+  nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
+  if (!promise) {
+    return nullptr;
+  }
+
+  nsRefPtr<InternalRequest> ir = ToInternalRequest(aRequest, IgnoreBody, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  AutoChildRequest request(this);
+  request.Add(ir, IgnoreBody, PassThroughReferrer, IgnoreInvalidScheme, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  PCacheQueryParams params;
+  ToPCacheQueryParams(params, aOptions);
+
+  RequestId requestId = AddRequestPromise(promise, aRv);
+
+  unused << mActor->SendDelete(requestId, request.SendAsRequest(), params);
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+Cache::Keys(const Optional<RequestOrUSVString>& aRequest,
+            const CacheQueryOptions& aOptions, ErrorResult& aRv)
+{
+  MOZ_ASSERT(mActor);
+
+  nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
+  if (!promise) {
+    return nullptr;
+  }
+
+  AutoChildRequest request(this);
+
+  if (aRequest.WasPassed()) {
+    nsRefPtr<InternalRequest> ir = ToInternalRequest(aRequest.Value(),
+                                                     IgnoreBody, aRv);
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+
+    request.Add(ir, IgnoreBody, PassThroughReferrer, IgnoreInvalidScheme, aRv);
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+  }
+
+  PCacheQueryParams params;
+  ToPCacheQueryParams(params, aOptions);
+
+  RequestId requestId = AddRequestPromise(promise, aRv);
+
+  unused << mActor->SendKeys(requestId, request.SendAsRequestOrVoid(), params);
+
+  return promise.forget();
+}
+
+// static
+bool
+Cache::PrefEnabled(JSContext* aCx, JSObject* aObj)
+{
+  using mozilla::dom::workers::WorkerPrivate;
+  using mozilla::dom::workers::GetWorkerPrivateFromContext;
+
+  // If we're on the main thread, then check the pref directly.
+  if (NS_IsMainThread()) {
+    bool enabled = false;
+    Preferences::GetBool("dom.caches.enabled", &enabled);
+    return enabled;
+  }
+
+  // Otherwise check the pref via the work private helper
+  WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+  if (!workerPrivate) {
+    return false;
+  }
+
+  return workerPrivate->DOMCachesEnabled();
+}
+
+nsISupports*
+Cache::GetParentObject() const
+{
+  return mGlobal;
+}
+
+JSObject*
+Cache::WrapObject(JSContext* aContext)
+{
+  return CacheBinding::Wrap(aContext, this);
+}
+
+void
+Cache::DestroyInternal(CacheChild* aActor)
+{
+  MOZ_ASSERT(mActor);
+  MOZ_ASSERT(mActor == aActor);
+  mActor->ClearListener();
+  mActor = nullptr;
+}
+
+void
+Cache::RecvMatchResponse(RequestId aRequestId, nsresult aRv,
+                         const PCacheResponseOrVoid& aResponse)
+{
+  // Convert the response immediately if its present.  This ensures that
+  // any stream actors are cleaned up, even if we error out below.
+  nsRefPtr<Response> response;
+  if (aResponse.type() == PCacheResponseOrVoid::TPCacheResponse) {
+    response = ToResponse(aResponse);
+  }
+
+  nsRefPtr<Promise> promise = RemoveRequestPromise(aRequestId);
+
+  if (NS_FAILED(aRv)) {
+    promise->MaybeReject(aRv);
+    return;
+  }
+
+  if (!response) {
+    promise->MaybeResolve(JS::UndefinedHandleValue);
+    return;
+  }
+
+  promise->MaybeResolve(response);
+}
+
+void
+Cache::RecvMatchAllResponse(RequestId aRequestId, nsresult aRv,
+                            const nsTArray<PCacheResponse>& aResponses)
+{
+  // Convert responses immediately.  This ensures that any stream actors are
+  // cleaned up, even if we error out below.
+  nsAutoTArray<nsRefPtr<Response>, 256> responses;
+  responses.SetCapacity(aResponses.Length());
+
+  for (uint32_t i = 0; i < aResponses.Length(); ++i) {
+    nsRefPtr<Response> response = ToResponse(aResponses[i]);
+    responses.AppendElement(response.forget());
+  }
+
+  nsRefPtr<Promise> promise = RemoveRequestPromise(aRequestId);
+
+  if (NS_FAILED(aRv)) {
+    promise->MaybeReject(aRv);
+    return;
+  }
+
+  promise->MaybeResolve(responses);
+}
+
+void
+Cache::RecvAddAllResponse(RequestId aRequestId, nsresult aRv)
+{
+  nsRefPtr<Promise> promise = RemoveRequestPromise(aRequestId);
+
+  if (NS_FAILED(aRv)) {
+    promise->MaybeReject(aRv);
+    return;
+  }
+
+  promise->MaybeResolve(JS::UndefinedHandleValue);
+}
+
+void
+Cache::RecvPutResponse(RequestId aRequestId, nsresult aRv)
+{
+  nsRefPtr<Promise> promise = RemoveRequestPromise(aRequestId);
+
+  if (NS_FAILED(aRv)) {
+    promise->MaybeReject(aRv);
+    return;
+  }
+
+  promise->MaybeResolve(JS::UndefinedHandleValue);
+}
+
+void
+Cache::RecvDeleteResponse(RequestId aRequestId, nsresult aRv, bool aSuccess)
+{
+  nsRefPtr<Promise> promise = RemoveRequestPromise(aRequestId);
+
+  if (NS_FAILED(aRv)) {
+    promise->MaybeReject(aRv);
+    return;
+  }
+
+  promise->MaybeResolve(aSuccess);
+}
+
+void
+Cache::RecvKeysResponse(RequestId aRequestId, nsresult aRv,
+                        const nsTArray<PCacheRequest>& aRequests)
+{
+  // Convert requests immediately.  This ensures that any stream actors are
+  // cleaned up, even if we error out below.
+  nsAutoTArray<nsRefPtr<Request>, 256> requests;
+  requests.SetCapacity(aRequests.Length());
+
+  for (uint32_t i = 0; i < aRequests.Length(); ++i) {
+    nsRefPtr<Request> request = ToRequest(aRequests[i]);
+    requests.AppendElement(request.forget());
+  }
+
+  nsRefPtr<Promise> promise = RemoveRequestPromise(aRequestId);
+
+  if (NS_FAILED(aRv)) {
+    promise->MaybeReject(aRv);
+    return;
+  }
+
+  promise->MaybeResolve(requests);
+}
+
+nsIGlobalObject*
+Cache::GetGlobalObject() const
+{
+  return mGlobal;
+}
+
+#ifdef DEBUG
+void
+Cache::AssertOwningThread() const
+{
+  NS_ASSERT_OWNINGTHREAD(Cache);
+}
+#endif
+
+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.
+}
+
+void
+Cache::RejectedCallback(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.
+}
+
+Cache::~Cache()
+{
+  if (mActor) {
+    mActor->StartDestroy();
+    // DestroyInternal() is called synchronously by StartDestroy().  So we
+    // should have already cleared the mActor.
+    MOZ_ASSERT(!mActor);
+  }
+}
+
+RequestId
+Cache::AddRequestPromise(Promise* aPromise, ErrorResult& aRv)
+{
+  MOZ_ASSERT(aPromise);
+  MOZ_ASSERT(!mRequestPromises.Contains(aPromise));
+
+  // Register ourself as a promise handler so that the promise will hold us
+  // alive.  This allows the client code to drop the ref to the Cache
+  // object and just keep their promise.  This is fairly common in promise
+  // chaining code.
+  aPromise->AppendNativeHandler(this);
+
+  mRequestPromises.AppendElement(aPromise);
+
+  // (Ab)use the promise pointer as our request ID.  This is a fast, thread-safe
+  // way to get a unique ID for the promise to be resolved later.
+  return reinterpret_cast<RequestId>(aPromise);
+}
+
+already_AddRefed<Promise>
+Cache::RemoveRequestPromise(RequestId aRequestId)
+{
+  MOZ_ASSERT(aRequestId != INVALID_REQUEST_ID);
+
+  for (uint32_t i = 0; i < mRequestPromises.Length(); ++i) {
+    nsRefPtr<Promise>& promise = mRequestPromises.ElementAt(i);
+    // To be safe, only cast promise pointers to our integer RequestId
+    // type and never cast an integer to a pointer.
+    if (aRequestId == reinterpret_cast<RequestId>(promise.get())) {
+      nsRefPtr<Promise> ref;
+      ref.swap(promise);
+      mRequestPromises.RemoveElementAt(i);
+      return ref.forget();
+    }
+  }
+  MOZ_ASSERT_UNREACHABLE("Received response without a matching promise!");
+  return nullptr;
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/Cache.h
@@ -0,0 +1,133 @@
+/* -*- 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_Cache_h
+#define mozilla_dom_cache_Cache_h
+
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/cache/Types.h"
+#include "mozilla/dom/cache/TypeUtils.h"
+#include "nsCOMPtr.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace ipc {
+  class IProtocol;
+}
+
+namespace dom {
+
+class OwningRequestOrUSVString;
+class Promise;
+struct CacheQueryOptions;
+class RequestOrUSVString;
+class Response;
+template<typename T> class Optional;
+template<typename T> class Sequence;
+
+namespace cache {
+
+class CacheChild;
+class PCacheRequest;
+class PCacheRequestOrVoid;
+class PCacheResponse;
+class PCacheResponseOrVoid;
+class PCacheStreamControlChild;
+
+class Cache MOZ_FINAL : public PromiseNativeHandler
+                      , 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);
+  already_AddRefed<Promise>
+  MatchAll(const Optional<RequestOrUSVString>& aRequest,
+           const CacheQueryOptions& aOptions, ErrorResult& aRv);
+  already_AddRefed<Promise>
+  Add(const RequestOrUSVString& aRequest, ErrorResult& aRv);
+  already_AddRefed<Promise>
+  AddAll(const Sequence<OwningRequestOrUSVString>& aRequests,
+         ErrorResult& aRv);
+  already_AddRefed<Promise>
+  Put(const RequestOrUSVString& aRequest, Response& aResponse,
+      ErrorResult& aRv);
+  already_AddRefed<Promise>
+  Delete(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions,
+         ErrorResult& aRv);
+  already_AddRefed<Promise>
+  Keys(const Optional<RequestOrUSVString>& aRequest,
+       const CacheQueryOptions& aParams, ErrorResult& aRv);
+
+  // binding methods
+  static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
+
+  nsISupports* GetParentObject() const;
+  virtual JSObject* WrapObject(JSContext* aContext) MOZ_OVERRIDE;
+
+  // Called when CacheChild actor is being destroyed
+  void DestroyInternal(CacheChild* aActor);
+
+  // methods forwarded from CacheChild
+  void RecvMatchResponse(RequestId aRequestId, nsresult aRv,
+                         const PCacheResponseOrVoid& aResponse);
+  void RecvMatchAllResponse(RequestId aRequestId, nsresult aRv,
+                            const nsTArray<PCacheResponse>& aResponses);
+  void RecvAddAllResponse(RequestId aRequestId, nsresult aRv);
+  void RecvPutResponse(RequestId aRequestId, nsresult aRv);
+
+  void RecvDeleteResponse(RequestId aRequestId, nsresult aRv,
+                          bool aSuccess);
+  void RecvKeysResponse(RequestId aRequestId, nsresult aRv,
+                        const nsTArray<PCacheRequest>& aRequests);
+
+  // TypeUtils methods
+  virtual nsIGlobalObject*
+  GetGlobalObject() const MOZ_OVERRIDE;
+
+#ifdef DEBUG
+  virtual void AssertOwningThread() const MOZ_OVERRIDE;
+#endif
+
+  // PromiseNativeHandler methods
+  virtual void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) MOZ_OVERRIDE;
+
+  virtual void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) MOZ_OVERRIDE;
+
+private:
+  ~Cache();
+
+  // TODO: Replace with actor-per-request model during refactor (bug 1110485)
+  RequestId AddRequestPromise(Promise* aPromise, ErrorResult& aRv);
+  already_AddRefed<Promise> RemoveRequestPromise(RequestId aRequestId);
+
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+  CacheChild* mActor;
+  nsTArray<nsRefPtr<Promise>> mRequestPromises;
+
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Cache)
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Cache_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheChild.cpp
@@ -0,0 +1,187 @@
+/* -*- 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/CacheChild.h"
+
+#include "mozilla/unused.h"
+#include "mozilla/dom/cache/ActorUtils.h"
+#include "mozilla/dom/cache/Cache.h"
+#include "mozilla/dom/cache/StreamUtils.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+// Declared in ActorUtils.h
+PCacheChild*
+AllocPCacheChild()
+{
+  return new CacheChild();
+}
+
+// Declared in ActorUtils.h
+void
+DeallocPCacheChild(PCacheChild* aActor)
+{
+  delete aActor;
+}
+
+CacheChild::CacheChild()
+  : mListener(nullptr)
+{
+  MOZ_COUNT_CTOR(cache::CacheChild);
+}
+
+CacheChild::~CacheChild()
+{
+  MOZ_COUNT_DTOR(cache::CacheChild);
+  NS_ASSERT_OWNINGTHREAD(CacheChild);
+  MOZ_ASSERT(!mListener);
+}
+
+void
+CacheChild::SetListener(Cache* aListener)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheChild);
+  MOZ_ASSERT(!mListener);
+  mListener = aListener;
+  MOZ_ASSERT(mListener);
+}
+
+void
+CacheChild::ClearListener()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheChild);
+  MOZ_ASSERT(mListener);
+  mListener = nullptr;
+}
+
+void
+CacheChild::StartDestroy()
+{
+  nsRefPtr<Cache> listener = mListener;
+
+  // StartDestroy() can get called from either Cache or the Feature.
+  // Theoretically we can get double called if the right race happens.  Handle
+  // that by just ignoring the second StartDestroy() call.
+  if (!listener) {
+    return;
+  }
+
+  listener->DestroyInternal(this);
+
+  // Cache listener should call ClearListener() in DestroyInternal()
+  MOZ_ASSERT(!mListener);
+
+  // Start actor destruction from parent process
+  unused << SendTeardown();
+}
+
+void
+CacheChild::ActorDestroy(ActorDestroyReason aReason)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheChild);
+  nsRefPtr<Cache> listener = mListener;
+  if (listener) {
+    listener->DestroyInternal(this);
+    // Cache listener should call ClearListener() in DestroyInternal()
+    MOZ_ASSERT(!mListener);
+  }
+
+  RemoveFeature();
+}
+
+bool
+CacheChild::RecvMatchResponse(const RequestId& requestId, const nsresult& aRv,
+                              const PCacheResponseOrVoid& aResponse)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheChild);
+
+  AddFeatureToStreamChild(aResponse, GetFeature());
+
+  nsRefPtr<Cache> listener = mListener;
+  if (!listener) {
+    StartDestroyStreamChild(aResponse);
+    return true;
+  }
+
+  listener->RecvMatchResponse(requestId, aRv, aResponse);
+  return true;
+}
+
+bool
+CacheChild::RecvMatchAllResponse(const RequestId& requestId, const nsresult& aRv,
+                                 nsTArray<PCacheResponse>&& aResponses)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheChild);
+
+  AddFeatureToStreamChild(aResponses, GetFeature());
+
+  nsRefPtr<Cache> listener = mListener;
+  if (!listener) {
+    StartDestroyStreamChild(aResponses);
+    return true;
+  }
+
+  listener->RecvMatchAllResponse(requestId, aRv, aResponses);
+  return true;
+}
+
+bool
+CacheChild::RecvAddAllResponse(const RequestId& requestId, const nsresult& aRv)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheChild);
+  nsRefPtr<Cache> listener = mListener;
+  if (listener) {
+    listener->RecvAddAllResponse(requestId, aRv);
+  }
+  return true;
+}
+
+bool
+CacheChild::RecvPutResponse(const RequestId& aRequestId, const nsresult& aRv)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheChild);
+  nsRefPtr<Cache> listener = mListener;
+  if (listener) {
+    listener->RecvPutResponse(aRequestId, aRv);
+  }
+  return true;
+}
+
+bool
+CacheChild::RecvDeleteResponse(const RequestId& requestId, const nsresult& aRv,
+                               const bool& result)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheChild);
+  nsRefPtr<Cache> listener = mListener;
+  if (listener) {
+    listener->RecvDeleteResponse(requestId, aRv, result);
+  }
+  return true;
+}
+
+bool
+CacheChild::RecvKeysResponse(const RequestId& requestId, const nsresult& aRv,
+                             nsTArray<PCacheRequest>&& aRequests)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheChild);
+
+  AddFeatureToStreamChild(aRequests, GetFeature());
+
+  nsRefPtr<Cache> listener = mListener;
+  if (!listener) {
+    StartDestroyStreamChild(aRequests);
+    return true;
+  }
+
+  listener->RecvKeysResponse(requestId, aRv, aRequests);
+  return true;
+}
+
+} // namespace cache
+} // namespace dom
+} // namesapce mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheChild.h
@@ -0,0 +1,75 @@
+/* -*- 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_CacheChild_h
+#define mozilla_dom_cache_CacheChild_h
+
+#include "mozilla/dom/cache/ActorChild.h"
+#include "mozilla/dom/cache/PCacheChild.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class Cache;
+
+class CacheChild MOZ_FINAL : public PCacheChild
+                           , public ActorChild
+{
+public:
+  CacheChild();
+  ~CacheChild();
+
+  void SetListener(Cache* aListener);
+
+  // Must be called by the associated Cache listener in its ActorDestroy()
+  // method.  Also, Cache must Send__delete__() the actor in its destructor to
+  // trigger ActorDestroy() if it has not been called yet.
+  void ClearListener();
+
+  // ActorChild methods
+
+  // Synchronously call ActorDestroy on our Cache listener and then start the
+  // actor destruction asynchronously from the parent-side.
+  virtual void StartDestroy() MOZ_OVERRIDE;
+
+private:
+  // PCacheChild methods
+  virtual void
+  ActorDestroy(ActorDestroyReason aReason) MOZ_OVERRIDE;
+
+  virtual bool
+  RecvMatchResponse(const RequestId& requestId, const nsresult& aRv,
+                    const PCacheResponseOrVoid& aResponse) MOZ_OVERRIDE;
+  virtual bool
+  RecvMatchAllResponse(const RequestId& requestId, const nsresult& aRv,
+                       nsTArray<PCacheResponse>&& responses) MOZ_OVERRIDE;
+  virtual bool
+  RecvAddAllResponse(const RequestId& requestId,
+                     const nsresult& aRv) MOZ_OVERRIDE;
+  virtual bool
+  RecvPutResponse(const RequestId& aRequestId,
+                  const nsresult& aRv) MOZ_OVERRIDE;
+  virtual bool
+  RecvDeleteResponse(const RequestId& requestId, const nsresult& aRv,
+                     const bool& result) MOZ_OVERRIDE;
+  virtual bool
+  RecvKeysResponse(const RequestId& requestId, const nsresult& aRv,
+                   nsTArray<PCacheRequest>&& requests) MOZ_OVERRIDE;
+
+  // Use a weak ref so actor does not hold DOM object alive past content use.
+  // The Cache object must call ClearListener() to null this before its
+  // destroyed.
+  Cache* MOZ_NON_OWNING_REF mListener;
+
+  NS_DECL_OWNINGTHREAD
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_CacheChild_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheInitData.ipdlh
@@ -0,0 +1,24 @@
+/* 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 PBackgroundSharedTypes;
+
+using mozilla::dom::cache::Namespace from "mozilla/dom/cache/Types.h";
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+// Data needed to initialize a CacheStorage or Cache backend.  Don't put
+// this with the other types in PCacheTypes.ipdlh since we want to import
+// it into PBackground.ipdl.
+struct CacheInitData
+{
+  Namespace namespaceEnum;
+  PrincipalInfo principalInfo;
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheParent.cpp
@@ -0,0 +1,291 @@
+/* -*- 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/CacheParent.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/cache/AutoUtils.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"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+using mozilla::ipc::FileDescriptorSetParent;
+using mozilla::ipc::PFileDescriptorSetParent;
+
+// Declared in ActorUtils.h
+void
+DeallocPCacheParent(PCacheParent* aActor)
+{
+  delete aActor;
+}
+
+CacheParent::CacheParent(cache::Manager* aManager, CacheId aCacheId)
+  : mManager(aManager)
+  , mCacheId(aCacheId)
+{
+  MOZ_COUNT_CTOR(cache::CacheParent);
+  MOZ_ASSERT(mManager);
+  mManager->AddRefCacheId(mCacheId);
+}
+
+CacheParent::~CacheParent()
+{
+  MOZ_COUNT_DTOR(cache::CacheParent);
+  MOZ_ASSERT(!mManager);
+  MOZ_ASSERT(mFetchPutList.IsEmpty());
+}
+
+void
+CacheParent::ActorDestroy(ActorDestroyReason aReason)
+{
+  MOZ_ASSERT(mManager);
+  for (uint32_t i = 0; i < mFetchPutList.Length(); ++i) {
+    mFetchPutList[i]->ClearListener();
+  }
+  mFetchPutList.Clear();
+  mManager->RemoveListener(this);
+  mManager->ReleaseCacheId(mCacheId);
+  mManager = nullptr;
+}
+
+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;
+}
+
+bool
+CacheParent::RecvMatch(const RequestId& aRequestId, const PCacheRequest& aRequest,
+                       const PCacheQueryParams& aParams)
+{
+  MOZ_ASSERT(mManager);
+  mManager->CacheMatch(this, aRequestId, mCacheId, aRequest,
+                       aParams);
+  return true;
+}
+
+bool
+CacheParent::RecvMatchAll(const RequestId& aRequestId,
+                          const PCacheRequestOrVoid& aRequest,
+                          const PCacheQueryParams& aParams)
+{
+  MOZ_ASSERT(mManager);
+  mManager->CacheMatchAll(this, aRequestId, mCacheId, aRequest, aParams);
+  return true;
+}
+
+bool
+CacheParent::RecvAddAll(const RequestId& aRequestId,
+                        nsTArray<PCacheRequest>&& aRequests)
+{
+  nsAutoTArray<nsCOMPtr<nsIInputStream>, 256> requestStreams;
+  requestStreams.SetCapacity(aRequests.Length());
+
+  for (uint32_t i = 0; i < aRequests.Length(); ++i) {
+    requestStreams.AppendElement(DeserializeCacheStream(aRequests[i].body()));
+  }
+
+  nsRefPtr<FetchPut> fetchPut;
+  nsresult rv = FetchPut::Create(this, mManager, aRequestId, mCacheId,
+                                 aRequests, requestStreams,
+                                 getter_AddRefs(fetchPut));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (!SendAddAllResponse(aRequestId, rv)) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("Cache failed to send AddAll response.");
+    }
+    return true;
+  }
+
+  mFetchPutList.AppendElement(fetchPut.forget());
+
+  return true;
+}
+
+bool
+CacheParent::RecvPut(const RequestId& aRequestId,
+                     const CacheRequestResponse& aPut)
+{
+  MOZ_ASSERT(mManager);
+
+  nsAutoTArray<CacheRequestResponse, 1> putList;
+  putList.AppendElement(aPut);
+
+  nsAutoTArray<nsCOMPtr<nsIInputStream>, 1> requestStreamList;
+  nsAutoTArray<nsCOMPtr<nsIInputStream>, 1> responseStreamList;
+
+  requestStreamList.AppendElement(
+    DeserializeCacheStream(aPut.request().body()));
+  responseStreamList.AppendElement(
+    DeserializeCacheStream(aPut.response().body()));
+
+
+  mManager->CachePutAll(this, aRequestId, mCacheId, putList, requestStreamList,
+                        responseStreamList);
+
+  return true;
+}
+
+bool
+CacheParent::RecvDelete(const RequestId& aRequestId,
+                        const PCacheRequest& aRequest,
+                        const PCacheQueryParams& aParams)
+{
+  MOZ_ASSERT(mManager);
+  mManager->CacheDelete(this, aRequestId, mCacheId, aRequest, aParams);
+  return true;
+}
+
+bool
+CacheParent::RecvKeys(const RequestId& aRequestId,
+                      const PCacheRequestOrVoid& aRequest,
+                      const PCacheQueryParams& aParams)
+{
+  MOZ_ASSERT(mManager);
+  mManager->CacheKeys(this, aRequestId, mCacheId, aRequest, aParams);
+  return true;
+}
+
+void
+CacheParent::OnCacheMatch(RequestId aRequestId, nsresult aRv,
+                          const SavedResponse* aSavedResponse,
+                          StreamList* aStreamList)
+{
+  AutoParentResponseOrVoid response(Manager());
+
+  // no match
+  if (NS_FAILED(aRv) || !aSavedResponse || !aStreamList) {
+    if (!SendMatchResponse(aRequestId, aRv, response.SendAsResponseOrVoid())) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("Cache failed to send Match response.");
+    }
+    return;
+  }
+
+  if (aSavedResponse) {
+    response.Add(*aSavedResponse, aStreamList);
+  }
+
+  if (!SendMatchResponse(aRequestId, aRv, response.SendAsResponseOrVoid())) {
+    // child process is gone, warn and allow actor to clean up normally
+    NS_WARNING("Cache failed to send Match response.");
+  }
+}
+
+void
+CacheParent::OnCacheMatchAll(RequestId aRequestId, nsresult aRv,
+                             const nsTArray<SavedResponse>& aSavedResponses,
+                             StreamList* aStreamList)
+{
+  AutoParentResponseList responses(Manager(), aSavedResponses.Length());
+
+  for (uint32_t i = 0; i < aSavedResponses.Length(); ++i) {
+    responses.Add(aSavedResponses[i], aStreamList);
+  }
+
+  if (!SendMatchAllResponse(aRequestId, aRv, responses.SendAsResponseList())) {
+    // child process is gone, warn and allow actor to clean up normally
+    NS_WARNING("Cache failed to send MatchAll response.");
+  }
+}
+
+void
+CacheParent::OnCachePutAll(RequestId aRequestId, nsresult aRv)
+{
+  if (!SendPutResponse(aRequestId, aRv)) {
+    // child process is gone, warn and allow actor to clean up normally
+    NS_WARNING("Cache failed to send Put response.");
+  }
+}
+
+void
+CacheParent::OnCacheDelete(RequestId aRequestId, nsresult aRv, bool aSuccess)
+{
+  if (!SendDeleteResponse(aRequestId, aRv, aSuccess)) {
+    // child process is gone, warn and allow actor to clean up normally
+    NS_WARNING("Cache failed to send Delete response.");
+  }
+}
+
+void
+CacheParent::OnCacheKeys(RequestId aRequestId, nsresult aRv,
+                         const nsTArray<SavedRequest>& aSavedRequests,
+                         StreamList* aStreamList)
+{
+  AutoParentRequestList requests(Manager(), aSavedRequests.Length());
+
+  for (uint32_t i = 0; i < aSavedRequests.Length(); ++i) {
+    requests.Add(aSavedRequests[i], aStreamList);
+  }
+
+  if (!SendKeysResponse(aRequestId, aRv, requests.SendAsRequestList())) {
+    // child process is gone, warn and allow actor to clean up normally
+    NS_WARNING("Cache failed to send Keys response.");
+  }
+}
+
+void
+CacheParent::OnFetchPut(FetchPut* aFetchPut, RequestId aRequestId, nsresult aRv)
+{
+  aFetchPut->ClearListener();
+  mFetchPutList.RemoveElement(aFetchPut);
+  if (!SendAddAllResponse(aRequestId, aRv)) {
+    // child process is gone, warn and allow actor to clean up normally
+    NS_WARNING("Cache failed to send AddAll response.");
+  }
+}
+
+already_AddRefed<nsIInputStream>
+CacheParent::DeserializeCacheStream(const PCacheReadStreamOrVoid& aStreamOrVoid)
+{
+  if (aStreamOrVoid.type() == PCacheReadStreamOrVoid::Tvoid_t) {
+    return nullptr;
+  }
+
+  const PCacheReadStream& readStream = aStreamOrVoid.get_PCacheReadStream();
+
+  nsCOMPtr<nsIInputStream> stream = ReadStream::Create(readStream);
+  if (stream) {
+    return stream.forget();
+  }
+
+  nsAutoTArray<FileDescriptor, 4> fds;
+  if (readStream.fds().type() ==
+      OptionalFileDescriptorSet::TPFileDescriptorSetChild) {
+
+    FileDescriptorSetParent* fdSetActor =
+      static_cast<FileDescriptorSetParent*>(readStream.fds().get_PFileDescriptorSetParent());
+    MOZ_ASSERT(fdSetActor);
+
+    fdSetActor->ForgetFileDescriptors(fds);
+    MOZ_ASSERT(!fds.IsEmpty());
+
+    if (!fdSetActor->Send__delete__(fdSetActor)) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("Cache failed to delete fd set actor.");
+    }
+  }
+
+  return DeserializeInputStream(readStream.params(), fds);
+}
+
+} // namespace cache
+} // namespace dom
+} // namesapce mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheParent.h
@@ -0,0 +1,87 @@
+/* -*- 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_CacheParent_h
+#define mozilla_dom_cache_CacheParent_h
+
+#include "mozilla/dom/cache/FetchPut.h"
+#include "mozilla/dom/cache/Manager.h"
+#include "mozilla/dom/cache/PCacheParent.h"
+#include "mozilla/dom/cache/Types.h"
+
+struct nsID;
+template <class T> class nsRefPtr;
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class CacheDBConnection;
+class CacheStreamControlParent;
+struct SavedResponse;
+struct StreamHolder;
+
+class CacheParent MOZ_FINAL : public PCacheParent
+                            , public Manager::Listener
+                            , public FetchPut::Listener
+{
+public:
+  CacheParent(cache::Manager* aManager, CacheId aCacheId);
+  virtual ~CacheParent();
+
+private:
+  // PCacheParent method
+  virtual void ActorDestroy(ActorDestroyReason aReason) MOZ_OVERRIDE;
+  virtual bool RecvTeardown() MOZ_OVERRIDE;
+  virtual bool
+  RecvMatch(const RequestId& aRequestId, const PCacheRequest& aRequest,
+            const PCacheQueryParams& aParams) MOZ_OVERRIDE;
+  virtual bool
+  RecvMatchAll(const RequestId& aRequestId, const PCacheRequestOrVoid& aRequest,
+               const PCacheQueryParams& aParams) MOZ_OVERRIDE;
+  virtual bool
+  RecvAddAll(const RequestId& aRequestId,
+             nsTArray<PCacheRequest>&& aRequests) MOZ_OVERRIDE;
+  virtual bool
+  RecvPut(const RequestId& aRequestId, const CacheRequestResponse& aPut);
+  virtual bool
+  RecvDelete(const RequestId& aRequestId, const PCacheRequest& aRequest,
+             const PCacheQueryParams& aParams) MOZ_OVERRIDE;
+  virtual bool
+  RecvKeys(const RequestId& aRequestId, const PCacheRequestOrVoid& aRequest,
+           const PCacheQueryParams& aParams) MOZ_OVERRIDE;
+
+  // Manager::Listener methods
+  virtual void OnCacheMatch(RequestId aRequestId, nsresult aRv,
+                            const SavedResponse* aSavedResponse,
+                            StreamList* aStreamList) MOZ_OVERRIDE;
+  virtual void OnCacheMatchAll(RequestId aRequestId, nsresult aRv,
+                               const nsTArray<SavedResponse>& aSavedResponses,
+                               StreamList* aStreamList) MOZ_OVERRIDE;
+  virtual void OnCachePutAll(RequestId aRequestId, nsresult aRv) MOZ_OVERRIDE;
+  virtual void OnCacheDelete(RequestId aRequestId, nsresult aRv,
+                             bool aSuccess) MOZ_OVERRIDE;
+  virtual void OnCacheKeys(RequestId aRequestId, nsresult aRv,
+                           const nsTArray<SavedRequest>& aSavedRequests,
+                           StreamList* aStreamList) MOZ_OVERRIDE;
+
+  // FetchPut::Listener methods
+  virtual void OnFetchPut(FetchPut* aFetchPut, RequestId aRequestId,
+                          nsresult aRv) MOZ_OVERRIDE;
+
+  already_AddRefed<nsIInputStream>
+  DeserializeCacheStream(const PCacheReadStreamOrVoid& aStreamOrVoid);
+
+  nsRefPtr<cache::Manager> mManager;
+  const CacheId mCacheId;
+  nsTArray<nsRefPtr<FetchPut>> mFetchPutList;
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_CacheParent_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheStorage.cpp
@@ -0,0 +1,631 @@
+/* -*- 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/CacheStorage.h"
+
+#include "mozilla/unused.h"
+#include "mozilla/dom/CacheStorageBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/cache/AutoUtils.h"
+#include "mozilla/dom/cache/Cache.h"
+#include "mozilla/dom/cache/CacheChild.h"
+#include "mozilla/dom/cache/CacheStorageChild.h"
+#include "mozilla/dom/cache/Feature.h"
+#include "mozilla/dom/cache/PCacheChild.h"
+#include "mozilla/dom/cache/ReadStream.h"
+#include "mozilla/dom/cache/TypeUtils.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsIGlobalObject.h"
+#include "nsIScriptSecurityManager.h"
+#include "WorkerPrivate.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+using mozilla::unused;
+using mozilla::ErrorResult;
+using mozilla::dom::workers::WorkerPrivate;
+using mozilla::ipc::BackgroundChild;
+using mozilla::ipc::PBackgroundChild;
+using mozilla::ipc::IProtocol;
+using mozilla::ipc::PrincipalInfo;
+using mozilla::ipc::PrincipalToPrincipalInfo;
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::cache::CacheStorage);
+NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::cache::CacheStorage);
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CacheStorage, mGlobal,
+                                                    mRequestPromises)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CacheStorage)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
+NS_INTERFACE_MAP_END
+
+// static
+already_AddRefed<CacheStorage>
+CacheStorage::CreateOnMainThread(Namespace aNamespace, nsIGlobalObject* aGlobal,
+                                 nsIPrincipal* aPrincipal, ErrorResult& aRv)
+{
+  MOZ_ASSERT(aGlobal);
+  MOZ_ASSERT(aPrincipal);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  bool nullPrincipal;
+  nsresult rv = aPrincipal->GetIsNullPrincipal(&nullPrincipal);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  if (nullPrincipal) {
+    NS_WARNING("CacheStorage not supported on null principal.");
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  // An unknown appId means that this principal was created for the codebase
+  // without all the security information from the end document or worker.
+  // We require exact knowledge of this information before allowing the
+  // caller to touch the disk using the Cache API.
+  bool unknownAppId = false;
+  aPrincipal->GetUnknownAppId(&unknownAppId);
+  if (unknownAppId) {
+    NS_WARNING("CacheStorage not supported on principal with unknown appId.");
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  PrincipalInfo principalInfo;
+  rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  nsRefPtr<CacheStorage> ref = new CacheStorage(aNamespace, aGlobal,
+                                                principalInfo, nullptr);
+  return ref.forget();
+}
+
+// static
+already_AddRefed<CacheStorage>
+CacheStorage::CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal,
+                             WorkerPrivate* aWorkerPrivate, ErrorResult& aRv)
+{
+  MOZ_ASSERT(aGlobal);
+  MOZ_ASSERT(aWorkerPrivate);
+  aWorkerPrivate->AssertIsOnWorkerThread();
+
+  nsRefPtr<Feature> feature = Feature::Create(aWorkerPrivate);
+  if (!feature) {
+    NS_WARNING("Worker thread is shutting down.");
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  const PrincipalInfo& principalInfo = aWorkerPrivate->GetPrincipalInfo();
+  if (principalInfo.type() == PrincipalInfo::TNullPrincipalInfo) {
+    NS_WARNING("CacheStorage not supported on null principal.");
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  if (principalInfo.type() == PrincipalInfo::TContentPrincipalInfo &&
+      principalInfo.get_ContentPrincipalInfo().appId() ==
+      nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+    NS_WARNING("CacheStorage not supported on principal with unknown appId.");
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<CacheStorage> ref = new CacheStorage(aNamespace, aGlobal,
+                                                principalInfo, feature);
+  return ref.forget();
+}
+
+CacheStorage::CacheStorage(Namespace aNamespace, nsIGlobalObject* aGlobal,
+                           const PrincipalInfo& aPrincipalInfo, Feature* aFeature)
+  : mNamespace(aNamespace)
+  , mGlobal(aGlobal)
+  , mPrincipalInfo(MakeUnique<PrincipalInfo>(aPrincipalInfo))
+  , mFeature(aFeature)
+  , mActor(nullptr)
+  , mFailedActor(false)
+{
+  MOZ_ASSERT(mGlobal);
+
+  // If the PBackground actor is already initialized then we can
+  // immediately use it
+  PBackgroundChild* actor = BackgroundChild::GetForCurrentThread();
+  if (actor) {
+    ActorCreated(actor);
+    return;
+  }
+
+  // Otherwise we must begin the PBackground initialization process and
+  // wait for the async ActorCreated() callback.
+  MOZ_ASSERT(NS_IsMainThread());
+  bool ok = BackgroundChild::GetOrCreateForCurrentThread(this);
+  if (!ok) {
+    ActorFailed();
+  }
+}
+
+already_AddRefed<Promise>
+CacheStorage::Match(const RequestOrUSVString& aRequest,
+                    const CacheQueryOptions& aOptions, ErrorResult& aRv)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+
+  nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
+  if (!promise) {
+    return nullptr;
+  }
+
+  if (mFailedActor) {
+    promise->MaybeReject(NS_ERROR_UNEXPECTED);
+    return promise.forget();
+  }
+
+  RequestId requestId = AddRequestPromise(promise, aRv);
+
+  Entry entry;
+  entry.mRequestId = requestId;
+  entry.mOp = OP_MATCH;
+  entry.mOptions = aOptions;
+  entry.mRequest = ToInternalRequest(aRequest, IgnoreBody, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  mPendingRequests.AppendElement(entry);
+
+  MaybeRunPendingRequests();
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+CacheStorage::Has(const nsAString& aKey, ErrorResult& aRv)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+
+  nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
+  if (!promise) {
+    return nullptr;
+  }
+
+  if (mFailedActor) {
+    promise->MaybeReject(NS_ERROR_UNEXPECTED);
+    return promise.forget();
+  }
+
+  RequestId requestId = AddRequestPromise(promise, aRv);
+
+  Entry* entry = mPendingRequests.AppendElement();
+  entry->mRequestId = requestId;
+  entry->mOp = OP_HAS;
+  entry->mKey = aKey;
+
+  MaybeRunPendingRequests();
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+CacheStorage::Open(const nsAString& aKey, ErrorResult& aRv)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+
+  nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
+  if (!promise) {
+    return nullptr;
+  }
+
+  if (mFailedActor) {
+    promise->MaybeReject(NS_ERROR_UNEXPECTED);
+    return promise.forget();
+  }
+
+  RequestId requestId = AddRequestPromise(promise, aRv);
+
+  Entry* entry = mPendingRequests.AppendElement();
+  entry->mRequestId = requestId;
+  entry->mOp = OP_OPEN;
+  entry->mKey = aKey;
+
+  MaybeRunPendingRequests();
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+CacheStorage::Delete(const nsAString& aKey, ErrorResult& aRv)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+
+  nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
+  if (!promise) {
+    return nullptr;
+  }
+
+  if (mFailedActor) {
+    promise->MaybeReject(NS_ERROR_UNEXPECTED);
+    return promise.forget();
+  }
+
+  RequestId requestId = AddRequestPromise(promise, aRv);
+
+  Entry* entry = mPendingRequests.AppendElement();
+  entry->mRequestId = requestId;
+  entry->mOp = OP_DELETE;
+  entry->mKey = aKey;
+
+  MaybeRunPendingRequests();
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+CacheStorage::Keys(ErrorResult& aRv)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+
+  nsRefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
+  if (!promise) {
+    return nullptr;
+  }
+
+  if (mFailedActor) {
+    promise->MaybeReject(NS_ERROR_UNEXPECTED);
+    return promise.forget();
+  }
+
+  RequestId requestId = AddRequestPromise(promise, aRv);
+
+  Entry* entry = mPendingRequests.AppendElement();
+  entry->mRequestId = requestId;
+  entry->mOp = OP_KEYS;
+
+  MaybeRunPendingRequests();
+
+  return promise.forget();
+}
+
+// static
+bool
+CacheStorage::PrefEnabled(JSContext* aCx, JSObject* aObj)
+{
+  return Cache::PrefEnabled(aCx, aObj);
+}
+
+nsISupports*
+CacheStorage::GetParentObject() const
+{
+  return mGlobal;
+}
+
+JSObject*
+CacheStorage::WrapObject(JSContext* aContext)
+{
+  return mozilla::dom::CacheStorageBinding::Wrap(aContext, this);
+}
+
+void
+CacheStorage::ActorCreated(PBackgroundChild* aActor)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+  MOZ_ASSERT(aActor);
+
+  if (NS_WARN_IF(mFeature && mFeature->Notified())) {
+    ActorFailed();
+    return;
+  }
+
+  // Feature ownership is passed to the CacheStorageChild actor and any actors
+  // it may create.  The Feature will keep the worker thread alive until the
+  // actors can gracefully shutdown.
+  CacheStorageChild* newActor = new CacheStorageChild(this, mFeature);
+  PCacheStorageChild* constructedActor =
+    aActor->SendPCacheStorageConstructor(newActor, mNamespace, *mPrincipalInfo);
+
+  if (NS_WARN_IF(!constructedActor)) {
+    ActorFailed();
+    return;
+  }
+
+  mFeature = nullptr;
+
+  MOZ_ASSERT(constructedActor == newActor);
+  mActor = newActor;
+
+  MaybeRunPendingRequests();
+  MOZ_ASSERT(mPendingRequests.IsEmpty());
+}
+
+void
+CacheStorage::ActorFailed()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+  MOZ_ASSERT(!mFailedActor);
+
+  mFailedActor = true;
+  mFeature = nullptr;
+
+  for (uint32_t i = 0; i < mPendingRequests.Length(); ++i) {
+    RequestId requestId = mPendingRequests[i].mRequestId;
+    nsRefPtr<Promise> promise = RemoveRequestPromise(requestId);
+    promise->MaybeReject(NS_ERROR_UNEXPECTED);
+  }
+  mPendingRequests.Clear();
+}
+
+void
+CacheStorage::DestroyInternal(CacheStorageChild* aActor)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+  MOZ_ASSERT(mActor);
+  MOZ_ASSERT(mActor == aActor);
+  mActor->ClearListener();
+  mActor = nullptr;
+
+  // Note that we will never get an actor again in case another request is
+  // made before this object is destructed.
+  ActorFailed();
+}
+
+void
+CacheStorage::RecvMatchResponse(RequestId aRequestId, nsresult aRv,
+                                const PCacheResponseOrVoid& aResponse)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+
+  // Convert the response immediately if its present.  This ensures that
+  // any stream actors are cleaned up, even if we error out below.
+  nsRefPtr<Response> response;
+  if (aResponse.type() == PCacheResponseOrVoid::TPCacheResponse) {
+    response = ToResponse(aResponse);
+  }
+
+  nsRefPtr<Promise> promise = RemoveRequestPromise(aRequestId);
+
+  if (NS_FAILED(aRv)) {
+    promise->MaybeReject(aRv);
+    return;
+  }
+
+  // If cache name was specified in the request options and the cache does
+  // not exist, then an error code will already have been set.  If we
+  // still do not have a response, then we just resolve undefined like a
+  // normal Cache::Match.
+  if (!response) {
+    promise->MaybeResolve(JS::UndefinedHandleValue);
+    return;
+  }
+
+  promise->MaybeResolve(response);
+}
+
+void
+CacheStorage::RecvHasResponse(RequestId aRequestId, nsresult aRv, bool aSuccess)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+
+  nsRefPtr<Promise> promise = RemoveRequestPromise(aRequestId);
+
+  if (NS_FAILED(aRv)) {
+    promise->MaybeReject(aRv);
+    return;
+
+  }
+
+  promise->MaybeResolve(aSuccess);
+}
+
+void
+CacheStorage::RecvOpenResponse(RequestId aRequestId, nsresult aRv,
+                               CacheChild* aActor)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+
+  // Unlike most of our async callback Recv*() methods, this one gets back
+  // an actor.  We need to make sure to clean it up in case of error.
+
+  nsRefPtr<Promise> promise = RemoveRequestPromise(aRequestId);
+
+  if (NS_FAILED(aRv)) {
+    if (aActor) {
+      // We cannot use the CacheChild::StartDestroy() method because there
+      // is no Cache object associated with the actor yet.  Instead, just
+      // send the underlying Teardown message.
+      unused << aActor->SendTeardown();
+    }
+    promise->MaybeReject(aRv);
+    return;
+  }
+
+  if (!aActor) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    return;
+  }
+
+  nsRefPtr<Cache> cache = new Cache(mGlobal, aActor);
+  promise->MaybeResolve(cache);
+}
+
+void
+CacheStorage::RecvDeleteResponse(RequestId aRequestId, nsresult aRv,
+                                 bool aSuccess)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+
+  nsRefPtr<Promise> promise = RemoveRequestPromise(aRequestId);
+
+  if (NS_FAILED(aRv)) {
+    promise->MaybeReject(aRv);
+    return;
+  }
+
+  promise->MaybeResolve(aSuccess);
+}
+
+void
+CacheStorage::RecvKeysResponse(RequestId aRequestId, nsresult aRv,
+                               const nsTArray<nsString>& aKeys)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+
+  nsRefPtr<Promise> promise = RemoveRequestPromise(aRequestId);
+
+  if (NS_FAILED(aRv)) {
+    promise->MaybeReject(aRv);
+    return;
+  }
+
+  promise->MaybeResolve(aKeys);
+}
+
+nsIGlobalObject*
+CacheStorage::GetGlobalObject() const
+{
+  return mGlobal;
+}
+
+#ifdef DEBUG
+void
+CacheStorage::AssertOwningThread() const
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+}
+#endif
+
+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.
+}
+
+void
+CacheStorage::RejectedCallback(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.
+}
+
+CacheStorage::~CacheStorage()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+
+  if (mActor) {
+    mActor->StartDestroy();
+    // DestroyInternal() is called synchronously by StartDestroy().  So we
+    // should have already cleared the mActor.
+    MOZ_ASSERT(!mActor);
+  }
+}
+
+void
+CacheStorage::MaybeRunPendingRequests()
+{
+  if (!mActor) {
+    return;
+  }
+
+  for (uint32_t i = 0; i < mPendingRequests.Length(); ++i) {
+    // Note, the entry can be modified below due to Request/Response body
+    // being marked used.
+    Entry& entry = mPendingRequests[i];
+    RequestId requestId = entry.mRequestId;
+    switch(entry.mOp) {
+      case OP_MATCH:
+      {
+        AutoChildRequest request(this);
+        ErrorResult rv;
+        request.Add(entry.mRequest, IgnoreBody, PassThroughReferrer,
+                    IgnoreInvalidScheme, rv);
+        if (NS_WARN_IF(rv.Failed())) {
+          nsRefPtr<Promise> promise = RemoveRequestPromise(requestId);
+          promise->MaybeReject(rv);
+          break;
+        }
+
+        PCacheQueryParams params;
+        ToPCacheQueryParams(params, entry.mOptions);
+
+        unused << mActor->SendMatch(requestId, request.SendAsRequest(), params);
+        break;
+      }
+      case OP_HAS:
+        unused << mActor->SendHas(requestId, entry.mKey);
+        break;
+      case OP_OPEN:
+        unused << mActor->SendOpen(requestId, entry.mKey);
+        break;
+      case OP_DELETE:
+        unused << mActor->SendDelete(requestId, entry.mKey);
+        break;
+      case OP_KEYS:
+        unused << mActor->SendKeys(requestId);
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE("Unknown pending CacheStorage op.");
+    }
+  }
+  mPendingRequests.Clear();
+}
+
+RequestId
+CacheStorage::AddRequestPromise(Promise* aPromise, ErrorResult& aRv)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+  MOZ_ASSERT(aPromise);
+  MOZ_ASSERT(!mRequestPromises.Contains(aPromise));
+
+  // Register ourself as a promise handler so that the promise will hold us
+  // alive.  This allows the client code to drop the ref to the CacheStorage
+  // object and just keep their promise.  This is fairly common in promise
+  // chaining code.
+  aPromise->AppendNativeHandler(this);
+
+  mRequestPromises.AppendElement(aPromise);
+
+  // (Ab)use the promise pointer as our request ID.  This is a fast, thread-safe
+  // way to get a unique ID for the promise to be resolved later.
+  return reinterpret_cast<RequestId>(aPromise);
+}
+
+already_AddRefed<Promise>
+CacheStorage::RemoveRequestPromise(RequestId aRequestId)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorage);
+  MOZ_ASSERT(aRequestId != INVALID_REQUEST_ID);
+
+  for (uint32_t i = 0; i < mRequestPromises.Length(); ++i) {
+    nsRefPtr<Promise>& promise = mRequestPromises.ElementAt(i);
+    // To be safe, only cast promise pointers to our integer RequestId
+    // type and never cast an integer to a pointer.
+    if (aRequestId == reinterpret_cast<RequestId>(promise.get())) {
+      nsRefPtr<Promise> ref;
+      ref.swap(promise);
+      mRequestPromises.RemoveElementAt(i);
+      return ref.forget();
+    }
+  }
+  MOZ_ASSERT_UNREACHABLE("Received response without a matching promise!");
+  return nullptr;
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheStorage.h
@@ -0,0 +1,165 @@
+/* -*- 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_CacheStorage_h
+#define mozilla_dom_cache_CacheStorage_h
+
+#include "mozilla/dom/CacheBinding.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/cache/Types.h"
+#include "mozilla/dom/cache/TypeUtils.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace ipc {
+  class IProtocol;
+  class PrincipalInfo;
+}
+
+namespace dom {
+
+class Promise;
+
+namespace workers {
+  class WorkerPrivate;
+}
+
+namespace cache {
+
+class CacheChild;
+class CacheStorageChild;
+class Feature;
+class PCacheRequest;
+class PCacheResponseOrVoid;
+
+class CacheStorage MOZ_FINAL : public nsIIPCBackgroundChildCreateCallback
+                             , 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);
+
+  static already_AddRefed<CacheStorage>
+  CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal,
+                 workers::WorkerPrivate* aWorkerPrivate, ErrorResult& aRv);
+
+  // webidl interface methods
+  already_AddRefed<Promise> Match(const RequestOrUSVString& aRequest,
+                                  const CacheQueryOptions& aOptions,
+                                  ErrorResult& aRv);
+  already_AddRefed<Promise> Has(const nsAString& aKey, ErrorResult& aRv);
+  already_AddRefed<Promise> Open(const nsAString& aKey, ErrorResult& aRv);
+  already_AddRefed<Promise> Delete(const nsAString& aKey, ErrorResult& aRv);
+  already_AddRefed<Promise> Keys(ErrorResult& aRv);
+
+  // binding methods
+  static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
+
+  nsISupports* GetParentObject() const;
+  virtual JSObject* WrapObject(JSContext* aContext) MOZ_OVERRIDE;
+
+  // nsIIPCbackgroundChildCreateCallback methods
+  virtual void ActorCreated(PBackgroundChild* aActor) MOZ_OVERRIDE;
+  virtual void ActorFailed() MOZ_OVERRIDE;
+
+  // Called when CacheStorageChild actor is being destroyed
+  void DestroyInternal(CacheStorageChild* aActor);
+
+  // Methods forwarded from CacheStorageChild
+  void RecvMatchResponse(RequestId aRequestId, nsresult aRv,
+                         const PCacheResponseOrVoid& aResponse);
+  void RecvHasResponse(RequestId aRequestId, nsresult aRv, bool aSuccess);
+  void RecvOpenResponse(RequestId aRequestId, nsresult aRv,
+                        CacheChild* aActor);
+  void RecvDeleteResponse(RequestId aRequestId, nsresult aRv, bool aSuccess);
+  void RecvKeysResponse(RequestId aRequestId, nsresult aRv,
+                        const nsTArray<nsString>& aKeys);
+
+  // TypeUtils methods
+  virtual nsIGlobalObject* GetGlobalObject() const MOZ_OVERRIDE;
+#ifdef DEBUG
+  virtual void AssertOwningThread() const MOZ_OVERRIDE;
+#endif
+
+  // PromiseNativeHandler methods
+  virtual void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) MOZ_OVERRIDE;
+
+  virtual void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) MOZ_OVERRIDE;
+
+private:
+  CacheStorage(Namespace aNamespace, nsIGlobalObject* aGlobal,
+               const mozilla::ipc::PrincipalInfo& aPrincipalInfo, Feature* aFeature);
+  ~CacheStorage();
+
+  void MaybeRunPendingRequests();
+
+  RequestId AddRequestPromise(Promise* aPromise, ErrorResult& aRv);
+  already_AddRefed<Promise> RemoveRequestPromise(RequestId aRequestId);
+
+  // Would like to use CacheInitData here, but we cannot because
+  // its an IPC struct which breaks webidl by including windows.h.
+  const Namespace mNamespace;
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+  UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
+  nsRefPtr<Feature> mFeature;
+  CacheStorageChild* mActor;
+  nsTArray<nsRefPtr<Promise>> mRequestPromises;
+
+  enum Op
+  {
+    OP_MATCH,
+    OP_HAS,
+    OP_OPEN,
+    OP_DELETE,
+    OP_KEYS
+  };
+
+  struct Entry
+  {
+    RequestId mRequestId;
+    Op mOp;
+    // Would prefer to use PCacheRequest/PCacheCacheQueryOptions, but can't
+    // because they introduce a header dependency on windows.h which
+    // breaks the bindings build.
+    nsRefPtr<InternalRequest> mRequest;
+    CacheQueryOptions mOptions;
+    // It would also be nice to union the key with the match args above,
+    // but VS2013 doesn't like these types in unions because of copy
+    // constructors.
+    nsString mKey;
+  };
+
+  nsTArray<Entry> mPendingRequests;
+  bool mFailedActor;
+
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(CacheStorage,
+                                           nsIIPCBackgroundChildCreateCallback)
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_CacheStorage_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheStorageChild.cpp
@@ -0,0 +1,174 @@
+/* -*- 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/CacheStorageChild.h"
+
+#include "mozilla/unused.h"
+#include "mozilla/dom/cache/CacheChild.h"
+#include "mozilla/dom/cache/CacheStorage.h"
+#include "mozilla/dom/cache/StreamUtils.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+// declared in ActorUtils.h
+void
+DeallocPCacheStorageChild(PCacheStorageChild* aActor)
+{
+  delete aActor;
+}
+
+CacheStorageChild::CacheStorageChild(CacheStorage* aListener, Feature* aFeature)
+  : mListener(aListener)
+{
+  MOZ_COUNT_CTOR(cache::CacheStorageChild);
+  MOZ_ASSERT(mListener);
+
+  SetFeature(aFeature);
+}
+
+CacheStorageChild::~CacheStorageChild()
+{
+  MOZ_COUNT_DTOR(cache::CacheStorageChild);
+  NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
+  MOZ_ASSERT(!mListener);
+}
+
+void
+CacheStorageChild::ClearListener()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
+  MOZ_ASSERT(mListener);
+  mListener = nullptr;
+}
+
+void
+CacheStorageChild::StartDestroy()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
+
+  nsRefPtr<CacheStorage> listener = mListener;
+
+  // StartDestroy() can get called from either CacheStorage or the Feature.
+  // Theoretically we can get double called if the right race happens.  Handle
+  // that by just ignoring the second StartDestroy() call.
+  if (!listener) {
+    return;
+  }
+
+  listener->DestroyInternal(this);
+
+  // CacheStorage listener should call ClearListener() in DestroyInternal()
+  MOZ_ASSERT(!mListener);
+
+  // Start actor destruction from parent process
+  unused << SendTeardown();
+}
+
+void
+CacheStorageChild::ActorDestroy(ActorDestroyReason aReason)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
+  nsRefPtr<CacheStorage> listener = mListener;
+  if (listener) {
+    listener->DestroyInternal(this);
+    // CacheStorage listener should call ClearListener() in DestroyInternal()
+    MOZ_ASSERT(!mListener);
+  }
+
+  RemoveFeature();
+}
+
+bool
+CacheStorageChild::RecvMatchResponse(const RequestId& aRequestId,
+                                     const nsresult& aRv,
+                                     const PCacheResponseOrVoid& aResponseOrVoid)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
+
+  AddFeatureToStreamChild(aResponseOrVoid, GetFeature());
+
+  nsRefPtr<CacheStorage> listener = mListener;
+  if (!listener) {
+    StartDestroyStreamChild(aResponseOrVoid);
+    return true;
+  }
+
+  listener->RecvMatchResponse(aRequestId, aRv, aResponseOrVoid);
+
+  return true;
+}
+
+bool
+CacheStorageChild::RecvHasResponse(const RequestId& aRequestId,
+                                   const nsresult& aRv,
+                                   const bool& aSuccess)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
+  nsRefPtr<CacheStorage> listener = mListener;
+  if (listener) {
+    listener->RecvHasResponse(aRequestId, aRv, aSuccess);
+  }
+  return true;
+}
+
+bool
+CacheStorageChild::RecvOpenResponse(const RequestId& aRequestId,
+                                    const nsresult& aRv,
+                                    PCacheChild* aActor)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
+
+  nsRefPtr<CacheStorage> listener = mListener;
+  if (!listener || FeatureNotified()) {
+    if (aActor) {
+      unused << aActor->SendTeardown();
+    }
+    return true;
+  }
+
+  CacheChild* cacheChild = static_cast<CacheChild*>(aActor);
+
+  // Since FeatureNotified() returned false above, we are guaranteed that
+  // the feature won't try to shutdown the actor until after we create the
+  // Cache DOM object in the listener's RecvOpenResponse() method.  This
+  // is important because StartShutdown() expects a Cache object listener.
+  cacheChild->SetFeature(GetFeature());
+
+  listener->RecvOpenResponse(aRequestId, aRv, cacheChild);
+  return true;
+}
+
+bool
+CacheStorageChild::RecvDeleteResponse(const RequestId& aRequestId,
+                                      const nsresult& aRv,
+                                      const bool& aResult)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
+  nsRefPtr<CacheStorage> listener = mListener;
+  if (listener) {
+    listener->RecvDeleteResponse(aRequestId, aRv, aResult);
+  }
+  return true;
+}
+
+bool
+CacheStorageChild::RecvKeysResponse(const RequestId& aRequestId,
+                                    const nsresult& aRv,
+                                    nsTArray<nsString>&& aKeys)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStorageChild);
+  nsRefPtr<CacheStorage> listener = mListener;
+  if (listener) {
+    listener->RecvKeysResponse(aRequestId, aRv, aKeys);
+  }
+  return true;
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheStorageChild.h
@@ -0,0 +1,72 @@
+/* -*- 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_CacheStorageChild_h
+#define mozilla_dom_cache_CacheStorageChild_h
+
+#include "mozilla/dom/cache/ActorChild.h"
+#include "mozilla/dom/cache/Types.h"
+#include "mozilla/dom/cache/PCacheStorageChild.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class CacheStorage;
+class PCacheChild;
+class Feature;
+
+class CacheStorageChild MOZ_FINAL : public PCacheStorageChild
+                                  , 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
+  // called yet.
+  void ClearListener();
+
+  // ActorChild methods
+
+  // Synchronously call ActorDestroy on our CacheStorage listener and then start
+  // the actor destruction asynchronously from the parent-side.
+  virtual void StartDestroy() MOZ_OVERRIDE;
+
+private:
+  // PCacheStorageChild methods
+  virtual void ActorDestroy(ActorDestroyReason aReason) MOZ_OVERRIDE;
+  virtual bool RecvMatchResponse(const RequestId& aRequestId,
+                                 const nsresult& aRv,
+                                 const PCacheResponseOrVoid& response) MOZ_OVERRIDE;
+  virtual bool RecvHasResponse(const cache::RequestId& aRequestId,
+                               const nsresult& aRv,
+                               const bool& aSuccess) MOZ_OVERRIDE;
+  virtual bool RecvOpenResponse(const cache::RequestId& aRequestId,
+                                const nsresult& aRv,
+                                PCacheChild* aActor) MOZ_OVERRIDE;
+  virtual bool RecvDeleteResponse(const cache::RequestId& aRequestId,
+                                  const nsresult& aRv,
+                                  const bool& aResult) MOZ_OVERRIDE;
+  virtual bool RecvKeysResponse(const cache::RequestId& aRequestId,
+                                const nsresult& aRv,
+                                nsTArray<nsString>&& aKeys) MOZ_OVERRIDE;
+
+  // Use a weak ref so actor does not hold DOM object alive past content use.
+  // The CacheStorage object must call ClearListener() to null this before its
+  // destroyed.
+  CacheStorage* MOZ_NON_OWNING_REF mListener;
+
+  NS_DECL_OWNINGTHREAD
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_CacheStorageChild_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheStorageParent.cpp
@@ -0,0 +1,450 @@
+/* -*- 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/CacheStorageParent.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/cache/ActorUtils.h"
+#include "mozilla/dom/cache/AutoUtils.h"
+#include "mozilla/dom/cache/CacheParent.h"
+#include "mozilla/dom/cache/CacheStreamControlParent.h"
+#include "mozilla/dom/cache/Manager.h"
+#include "mozilla/dom/cache/ManagerId.h"
+#include "mozilla/dom/cache/ReadStream.h"
+#include "mozilla/dom/cache/SavedTypes.h"
+#include "mozilla/dom/cache/StreamList.h"
+#include "mozilla/ipc/PBackgroundParent.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/ipc/PFileDescriptorSetParent.h"
+#include "mozilla/DebugOnly.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+using mozilla::ipc::PBackgroundParent;
+using mozilla::ipc::PFileDescriptorSetParent;
+using mozilla::ipc::PrincipalInfo;
+
+// declared in ActorUtils.h
+PCacheStorageParent*
+AllocPCacheStorageParent(PBackgroundParent* aManagingActor,
+                         Namespace aNamespace,
+                         const mozilla::ipc::PrincipalInfo& aPrincipalInfo)
+{
+  return new CacheStorageParent(aManagingActor, aNamespace, aPrincipalInfo);
+}
+
+// declared in ActorUtils.h
+void
+DeallocPCacheStorageParent(PCacheStorageParent* aActor)
+{
+  delete aActor;
+}
+
+CacheStorageParent::CacheStorageParent(PBackgroundParent* aManagingActor,
+                                       Namespace aNamespace,
+                                       const PrincipalInfo& aPrincipalInfo)
+  : mNamespace(aNamespace)
+  , mVerifiedStatus(NS_OK)
+{
+  MOZ_COUNT_CTOR(cache::CacheStorageParent);
+  MOZ_ASSERT(aManagingActor);
+
+  // Start the async principal verification process immediately.
+  mVerifier = PrincipalVerifier::CreateAndDispatch(this, aManagingActor,
+                                                   aPrincipalInfo);
+  MOZ_ASSERT(mVerifier);
+}
+
+CacheStorageParent::~CacheStorageParent()
+{
+  MOZ_COUNT_DTOR(cache::CacheStorageParent);
+  MOZ_ASSERT(!mVerifier);
+  MOZ_ASSERT(!mManager);
+}
+
+void
+CacheStorageParent::ActorDestroy(ActorDestroyReason aReason)
+{
+  if (mVerifier) {
+    mVerifier->ClearListener();
+    mVerifier = nullptr;
+  }
+
+  if (mManager) {
+    MOZ_ASSERT(!mActiveRequests.IsEmpty());
+    mManager->RemoveListener(this);
+    mManager = nullptr;
+  }
+}
+
+bool
+CacheStorageParent::RecvTeardown()
+{
+  if (!Send__delete__(this)) {
+    // child process is gone, warn and allow actor to clean up normally
+    NS_WARNING("CacheStorage failed to delete actor.");
+  }
+  return true;
+}
+
+bool
+CacheStorageParent::RecvMatch(const RequestId& aRequestId,
+                              const PCacheRequest& aRequest,
+                              const PCacheQueryParams& aParams)
+{
+  if (NS_WARN_IF(NS_FAILED(mVerifiedStatus))) {
+    if (!SendMatchResponse(aRequestId, mVerifiedStatus, void_t())) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("CacheStorage failed to send Match response.");
+    }
+    return true;
+  }
+
+  // queue requests if we are still waiting for principal verification
+  if (!mManagerId) {
+    Entry* entry = mPendingRequests.AppendElement();
+    entry->mOp = OP_MATCH;
+    entry->mRequestId = aRequestId;
+    entry->mRequest = aRequest;
+    entry->mParams = aParams;
+    return true;
+  }
+
+  nsRefPtr<cache::Manager> manager;
+  nsresult rv = RequestManager(aRequestId, getter_AddRefs(manager));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (!SendMatchResponse(aRequestId, rv, void_t())) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("CacheStorage failed to send Match response.");
+    }
+    return true;
+  }
+
+  manager->StorageMatch(this, aRequestId, mNamespace, aRequest,
+                        aParams);
+
+  return true;
+}
+
+bool
+CacheStorageParent::RecvHas(const RequestId& aRequestId, const nsString& aKey)
+{
+  if (NS_WARN_IF(NS_FAILED(mVerifiedStatus))) {
+    if (!SendHasResponse(aRequestId, mVerifiedStatus, false)) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("CacheStorage failed to send Has response.");
+    }
+    return true;
+  }
+
+  // queue requests if we are still waiting for principal verification
+  if (!mManagerId) {
+    Entry* entry = mPendingRequests.AppendElement();
+    entry->mOp = OP_HAS;
+    entry->mRequestId = aRequestId;
+    entry->mKey = aKey;
+    return true;
+  }
+
+  nsRefPtr<cache::Manager> manager;
+  nsresult rv = RequestManager(aRequestId, getter_AddRefs(manager));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (!SendHasResponse(aRequestId, rv, false)) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("CacheStorage failed to send Has response.");
+    }
+    return true;
+  }
+
+  manager->StorageHas(this, aRequestId, mNamespace, aKey);
+
+  return true;
+}
+
+bool
+CacheStorageParent::RecvOpen(const RequestId& aRequestId, const nsString& aKey)
+{
+  if (NS_WARN_IF(NS_FAILED(mVerifiedStatus))) {
+    if (!SendOpenResponse(aRequestId, mVerifiedStatus, nullptr)) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("CacheStorage failed to send Open response.");
+    }
+    return true;
+  }
+
+  // queue requests if we are still waiting for principal verification
+  if (!mManagerId) {
+    Entry* entry = mPendingRequests.AppendElement();
+    entry->mOp = OP_OPEN;
+    entry->mRequestId = aRequestId;
+    entry->mKey = aKey;
+    return true;
+  }
+
+  nsRefPtr<cache::Manager> manager;
+  nsresult rv = RequestManager(aRequestId, getter_AddRefs(manager));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (!SendOpenResponse(aRequestId, rv, nullptr)) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("CacheStorage failed to send Open response.");
+    }
+    return true;
+  }
+
+  manager->StorageOpen(this, aRequestId, mNamespace, aKey);
+
+  return true;
+}
+
+bool
+CacheStorageParent::RecvDelete(const RequestId& aRequestId,
+                               const nsString& aKey)
+{
+  if (NS_WARN_IF(NS_FAILED(mVerifiedStatus))) {
+    if (!SendDeleteResponse(aRequestId, mVerifiedStatus, false)) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("CacheStorage failed to send Delete response.");
+    }
+    return true;
+  }
+
+  // queue requests if we are still waiting for principal verification
+  if (!mManagerId) {
+    Entry* entry = mPendingRequests.AppendElement();
+    entry->mOp = OP_DELETE;
+    entry->mRequestId = aRequestId;
+    entry->mKey = aKey;
+    return true;
+  }
+
+  nsRefPtr<cache::Manager> manager;
+  nsresult rv = RequestManager(aRequestId, getter_AddRefs(manager));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (!SendDeleteResponse(aRequestId, rv, false)) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("CacheStorage failed to send Delete response.");
+    }
+    return true;
+  }
+
+  manager->StorageDelete(this, aRequestId, mNamespace, aKey);
+
+  return true;
+}
+
+bool
+CacheStorageParent::RecvKeys(const RequestId& aRequestId)
+{
+  if (NS_WARN_IF(NS_FAILED(mVerifiedStatus))) {
+    if (!SendKeysResponse(aRequestId, mVerifiedStatus, nsTArray<nsString>())) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("CacheStorage failed to send Keys response.");
+    }
+  }
+
+  // queue requests if we are still waiting for principal verification
+  if (!mManagerId) {
+    Entry* entry = mPendingRequests.AppendElement();
+    entry->mOp = OP_DELETE;
+    entry->mRequestId = aRequestId;
+    return true;
+  }
+
+  nsRefPtr<cache::Manager> manager;
+  nsresult rv = RequestManager(aRequestId, getter_AddRefs(manager));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (!SendKeysResponse(aRequestId, rv, nsTArray<nsString>())) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("CacheStorage failed to send Keys response.");
+    }
+    return true;
+  }
+
+  manager->StorageKeys(this, aRequestId, mNamespace);
+
+  return true;
+}
+
+void
+CacheStorageParent::OnPrincipalVerified(nsresult aRv, ManagerId* aManagerId)
+{
+  MOZ_ASSERT(mVerifier);
+  MOZ_ASSERT(!mManagerId);
+  MOZ_ASSERT(!mManager);
+  MOZ_ASSERT(NS_SUCCEEDED(mVerifiedStatus));
+
+  if (NS_WARN_IF(NS_FAILED(aRv))) {
+    mVerifiedStatus = aRv;
+  }
+
+  mManagerId = aManagerId;
+  mVerifier->ClearListener();
+  mVerifier = nullptr;
+
+  RetryPendingRequests();
+}
+
+void
+CacheStorageParent::OnStorageMatch(RequestId aRequestId, nsresult aRv,
+                                   const SavedResponse* aSavedResponse,
+                                   StreamList* aStreamList)
+{
+  PCacheResponseOrVoid responseOrVoid;
+
+  ReleaseManager(aRequestId);
+
+  AutoParentResponseOrVoid response(Manager());
+
+  // no match
+  if (NS_FAILED(aRv) || !aSavedResponse) {
+    if (!SendMatchResponse(aRequestId, aRv, response.SendAsResponseOrVoid())) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("CacheStorage failed to send Match response.");
+    }
+    return;
+  }
+
+  if (aSavedResponse) {
+    response.Add(*aSavedResponse, aStreamList);
+  }
+
+  if (!SendMatchResponse(aRequestId, aRv, response.SendAsResponseOrVoid())) {
+    // child process is gone, warn and allow actor to clean up normally
+    NS_WARNING("CacheStorage failed to send Match response.");
+  }
+}
+
+void
+CacheStorageParent::OnStorageHas(RequestId aRequestId, nsresult aRv,
+                                 bool aCacheFound)
+{
+  ReleaseManager(aRequestId);
+  if (!SendHasResponse(aRequestId, aRv, aCacheFound)) {
+    // child process is gone, warn and allow actor to clean up normally
+    NS_WARNING("CacheStorage failed to send Has response.");
+  }
+}
+
+void
+CacheStorageParent::OnStorageOpen(RequestId aRequestId, nsresult aRv,
+                                  CacheId aCacheId)
+{
+  if (NS_FAILED(aRv)) {
+    ReleaseManager(aRequestId);
+    if (!SendOpenResponse(aRequestId, aRv, nullptr)) {
+      // child process is gone, warn and allow actor to clean up normally
+      NS_WARNING("CacheStorage failed to send Open response.");
+    }
+    return;
+  }
+
+  MOZ_ASSERT(mManager);
+  CacheParent* actor = new CacheParent(mManager, aCacheId);
+
+  ReleaseManager(aRequestId);
+
+  PCacheParent* base = Manager()->SendPCacheConstructor(actor);
+  actor = static_cast<CacheParent*>(base);
+  if (!SendOpenResponse(aRequestId, aRv, actor)) {
+    // child process is gone, warn and allow actor to clean up normally
+    NS_WARNING("CacheStorage failed to send Open response.");
+  }
+}
+
+void
+CacheStorageParent::OnStorageDelete(RequestId aRequestId, nsresult aRv,
+                                    bool aCacheDeleted)
+{
+  ReleaseManager(aRequestId);
+  if (!SendDeleteResponse(aRequestId, aRv, aCacheDeleted)) {
+    // child process is gone, warn and allow actor to clean up normally
+    NS_WARNING("CacheStorage failed to send Delete response.");
+  }
+}
+
+void
+CacheStorageParent::OnStorageKeys(RequestId aRequestId, nsresult aRv,
+                                  const nsTArray<nsString>& aKeys)
+{
+  ReleaseManager(aRequestId);
+  if (!SendKeysResponse(aRequestId, aRv, aKeys)) {
+    // child process is gone, warn and allow actor to clean up normally
+    NS_WARNING("CacheStorage failed to send Keys response.");
+  }
+}
+
+void
+CacheStorageParent::RetryPendingRequests()
+{
+  MOZ_ASSERT(mManagerId || NS_FAILED(mVerifiedStatus));
+  for (uint32_t i = 0; i < mPendingRequests.Length(); ++i) {
+    const Entry& entry = mPendingRequests[i];
+    switch(entry.mOp) {
+      case OP_MATCH:
+        RecvMatch(entry.mRequestId, entry.mRequest, entry.mParams);
+        break;
+      case OP_HAS:
+        RecvHas(entry.mRequestId, entry.mKey);
+        break;
+      case OP_OPEN:
+        RecvOpen(entry.mRequestId, entry.mKey);
+        break;
+      case OP_DELETE:
+        RecvDelete(entry.mRequestId, entry.mKey);
+        break;
+      case OP_KEYS:
+        RecvKeys(entry.mRequestId);
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE("Pending request within unknown op");
+    }
+  }
+  mPendingRequests.Clear();
+  mPendingRequests.Compact();
+}
+
+nsresult
+CacheStorageParent::RequestManager(RequestId aRequestId,
+                                   cache::Manager** aManagerOut)
+{
+  MOZ_ASSERT(!mActiveRequests.Contains(aRequestId));
+  nsRefPtr<cache::Manager> ref = mManager;
+  if (!ref) {
+    MOZ_ASSERT(mActiveRequests.IsEmpty());
+    nsresult rv = cache::Manager::GetOrCreate(mManagerId, getter_AddRefs(ref));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    mManager = ref;
+  }
+  mActiveRequests.AppendElement(aRequestId);
+  ref.forget(aManagerOut);
+  return NS_OK;
+}
+
+void
+CacheStorageParent::ReleaseManager(RequestId aRequestId)
+{
+  // Note that if the child process dies we also clean up the mManager in
+  // ActorDestroy().  There is no race with this method, however, because
+  // ActorDestroy removes this object from the Manager's listener list.
+  // Therefore ReleaseManager() should never be called after ActorDestroy()
+  // runs.
+  MOZ_ASSERT(mManager);
+  MOZ_ASSERT(!mActiveRequests.IsEmpty());
+
+  MOZ_ALWAYS_TRUE(mActiveRequests.RemoveElement(aRequestId));
+
+  if (mActiveRequests.IsEmpty()) {
+    mManager->RemoveListener(this);
+    mManager = nullptr;
+  }
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheStorageParent.h
@@ -0,0 +1,108 @@
+/* -*- 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_CacheStorageParent_h
+#define mozilla_dom_cache_CacheStorageParent_h
+
+#include "mozilla/dom/cache/CacheInitData.h"
+#include "mozilla/dom/cache/PCacheStorageParent.h"
+#include "mozilla/dom/cache/Manager.h"
+#include "mozilla/dom/cache/PrincipalVerifier.h"
+#include "mozilla/dom/cache/Types.h"
+
+template <class T> class nsRefPtr;
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class CacheStreamControlParent;
+class ManagerId;
+
+class CacheStorageParent MOZ_FINAL : public PCacheStorageParent
+                                   , public PrincipalVerifier::Listener
+                                   , public Manager::Listener
+{
+public:
+  CacheStorageParent(PBackgroundParent* aManagingActor, Namespace aNamespace,
+                     const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
+  virtual ~CacheStorageParent();
+
+private:
+  // PCacheStorageParent methods
+  virtual void ActorDestroy(ActorDestroyReason aReason) MOZ_OVERRIDE;
+  virtual bool RecvTeardown() MOZ_OVERRIDE;
+  virtual bool RecvMatch(const RequestId& aRequestId,
+                         const PCacheRequest& aRequest,
+                         const PCacheQueryParams& aParams) MOZ_OVERRIDE;
+  virtual bool RecvHas(const RequestId& aRequestId,
+                       const nsString& aKey) MOZ_OVERRIDE;
+  virtual bool RecvOpen(const RequestId& aRequestId,
+                        const nsString& aKey) MOZ_OVERRIDE;
+  virtual bool RecvDelete(const RequestId& aRequestId,
+                          const nsString& aKey) MOZ_OVERRIDE;
+  virtual bool RecvKeys(const RequestId& aRequestId) MOZ_OVERRIDE;
+
+  // PrincipalVerifier::Listener methods
+  virtual void OnPrincipalVerified(nsresult aRv,
+                                   ManagerId* aManagerId) MOZ_OVERRIDE;
+
+  // Manager::Listener methods
+  virtual void OnStorageMatch(RequestId aRequestId, nsresult aRv,
+                              const SavedResponse* aResponse,
+                              StreamList* aStreamList) MOZ_OVERRIDE;
+  virtual void OnStorageHas(RequestId aRequestId, nsresult aRv,
+                            bool aCacheFound) MOZ_OVERRIDE;
+  virtual void OnStorageOpen(RequestId aRequestId, nsresult aRv,
+                             CacheId aCacheId) MOZ_OVERRIDE;
+  virtual void OnStorageDelete(RequestId aRequestId, nsresult aRv,
+                               bool aCacheDeleted) MOZ_OVERRIDE;
+  virtual void OnStorageKeys(RequestId aRequestId, nsresult aRv,
+                             const nsTArray<nsString>& aKeys) MOZ_OVERRIDE;
+
+  CacheStreamControlParent*
+  SerializeReadStream(CacheStreamControlParent *aStreamControl, const nsID& aId,
+                      StreamList* aStreamList,
+                      PCacheReadStream* aReadStreamOut);
+
+  void RetryPendingRequests();
+
+  nsresult RequestManager(RequestId aRequestId, cache::Manager** aManagerOut);
+  void ReleaseManager(RequestId aRequestId);
+
+  const Namespace mNamespace;
+  nsRefPtr<PrincipalVerifier> mVerifier;
+  nsresult mVerifiedStatus;
+  nsRefPtr<ManagerId> mManagerId;
+  nsRefPtr<cache::Manager> mManager;
+
+  enum Op
+  {
+    OP_MATCH,
+    OP_HAS,
+    OP_OPEN,
+    OP_DELETE,
+    OP_KEYS
+  };
+
+  struct Entry
+  {
+    Op mOp;
+    RequestId mRequestId;
+    nsString mKey;
+    PCacheRequest mRequest;
+    PCacheQueryParams mParams;
+  };
+
+  nsTArray<Entry> mPendingRequests;
+  nsTArray<RequestId> mActiveRequests;
+};
+
+} // namesapce cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_CacheStorageParent_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheStreamControlChild.cpp
@@ -0,0 +1,137 @@
+/* -*- 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/CacheStreamControlChild.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/unused.h"
+#include "mozilla/dom/cache/ActorUtils.h"
+#include "mozilla/dom/cache/ReadStream.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+// declared in ActorUtils.h
+PCacheStreamControlChild*
+AllocPCacheStreamControlChild()
+{
+  return new CacheStreamControlChild();
+}
+
+// declared in ActorUtils.h
+void
+DeallocPCacheStreamControlChild(PCacheStreamControlChild* aActor)
+{
+  delete aActor;
+}
+
+CacheStreamControlChild::CacheStreamControlChild()
+  : mDestroyStarted(false)
+{
+  MOZ_COUNT_CTOR(cache::CacheStreamControlChild);
+}
+
+CacheStreamControlChild::~CacheStreamControlChild()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
+  MOZ_COUNT_DTOR(cache::CacheStreamControlChild);
+}
+
+void
+CacheStreamControlChild::AddListener(ReadStream* aListener)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
+  MOZ_ASSERT(aListener);
+  MOZ_ASSERT(!mListeners.Contains(aListener));
+  mListeners.AppendElement(aListener);
+}
+
+void
+CacheStreamControlChild::RemoveListener(ReadStream* aListener)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
+  MOZ_ASSERT(aListener);
+  MOZ_ALWAYS_TRUE(mListeners.RemoveElement(aListener));
+}
+
+void
+CacheStreamControlChild::NoteClosed(const nsID& aId)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
+  unused << SendNoteClosed(aId);
+}
+
+void
+CacheStreamControlChild::StartDestroy()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
+  // This can get called twice under some circumstances.  For example, if the
+  // actor is added to a Feature that has already been notified and the Cache
+  // actor has no mListener.
+  if (mDestroyStarted) {
+    return;
+  }
+  mDestroyStarted = true;
+
+  // Begin shutting down all streams.  This is the same as if the parent had
+  // asked us to shutdown.  So simulate the CloseAll IPC message.
+  RecvCloseAll();
+}
+
+void
+CacheStreamControlChild::ActorDestroy(ActorDestroyReason aReason)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
+  // Note, we cannot trigger IPC traffic here.  So use
+  // CloseStreamWithoutReporting().
+  ReadStreamList::ForwardIterator iter(mListeners);
+  while (iter.HasMore()) {
+    nsRefPtr<ReadStream> stream = iter.GetNext();
+    stream->CloseStreamWithoutReporting();
+  }
+  mListeners.Clear();
+
+  RemoveFeature();
+}
+
+bool
+CacheStreamControlChild::RecvClose(const nsID& aId)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
+  DebugOnly<uint32_t> closedCount = 0;
+
+  ReadStreamList::ForwardIterator iter(mListeners);
+  while (iter.HasMore()) {
+    nsRefPtr<ReadStream> stream = iter.GetNext();
+    // note, multiple streams may exist for same ID
+    if (stream->MatchId(aId)) {
+      stream->CloseStream();
+      closedCount += 1;
+    }
+  }
+
+  MOZ_ASSERT(closedCount > 0);
+
+  return true;
+}
+
+bool
+CacheStreamControlChild::RecvCloseAll()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild);
+  ReadStreamList::ForwardIterator iter(mListeners);
+  while (iter.HasMore()) {
+    nsRefPtr<ReadStream> stream = iter.GetNext();
+    stream->CloseStream();
+  }
+  return true;
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheStreamControlChild.h
@@ -0,0 +1,53 @@
+/* -*- 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_CacheStreamControlChild_h
+#define mozilla_dom_cache_CacheStreamControlChild_h
+
+#include "mozilla/dom/cache/ActorChild.h"
+#include "mozilla/dom/cache/PCacheStreamControlChild.h"
+#include "nsTObserverArray.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class ReadStream;
+
+class CacheStreamControlChild MOZ_FINAL : public PCacheStreamControlChild
+                                        , public ActorChild
+{
+public:
+  CacheStreamControlChild();
+  ~CacheStreamControlChild();
+
+  void AddListener(ReadStream* aListener);
+  void RemoveListener(ReadStream* aListener);
+
+  void NoteClosed(const nsID& aId);
+
+  // ActorChild methods
+  virtual void StartDestroy() MOZ_OVERRIDE;
+
+private:
+  // PCacheStreamControlChild methods
+  virtual void ActorDestroy(ActorDestroyReason aReason) MOZ_OVERRIDE;
+  virtual bool RecvClose(const nsID& aId) MOZ_OVERRIDE;
+  virtual bool RecvCloseAll() MOZ_OVERRIDE;
+
+  typedef nsTObserverArray<ReadStream*> ReadStreamList;
+  ReadStreamList mListeners;
+
+  bool mDestroyStarted;
+
+  NS_DECL_OWNINGTHREAD
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_CacheStreamControlChild_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheStreamControlParent.cpp
@@ -0,0 +1,147 @@
+/* -*- 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/CacheStreamControlParent.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/unused.h"
+#include "mozilla/dom/cache/ReadStream.h"
+#include "mozilla/dom/cache/StreamList.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+// declared in ActorUtils.h
+void
+DeallocPCacheStreamControlParent(PCacheStreamControlParent* aActor)
+{
+  delete aActor;
+}
+
+CacheStreamControlParent::CacheStreamControlParent()
+{
+  MOZ_COUNT_CTOR(cache::CacheStreamControlParent);
+}
+
+CacheStreamControlParent::~CacheStreamControlParent()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
+  MOZ_ASSERT(!mStreamList);
+  MOZ_COUNT_DTOR(cache::CacheStreamControlParent);
+}
+
+void
+CacheStreamControlParent::AddListener(ReadStream* aListener)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
+  MOZ_ASSERT(aListener);
+  MOZ_ASSERT(!mListeners.Contains(aListener));
+  mListeners.AppendElement(aListener);
+}
+
+void
+CacheStreamControlParent::RemoveListener(ReadStream* aListener)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
+  MOZ_ASSERT(aListener);
+  DebugOnly<bool> removed = mListeners.RemoveElement(aListener);
+  MOZ_ASSERT(removed);
+}
+
+void
+CacheStreamControlParent::ActorDestroy(ActorDestroyReason aReason)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
+  MOZ_ASSERT(mStreamList);
+  ReadStreamList::ForwardIterator iter(mListeners);
+  while (iter.HasMore()) {
+    nsRefPtr<ReadStream> stream = iter.GetNext();
+    stream->CloseStreamWithoutReporting();
+  }
+  mStreamList->RemoveStreamControl(this);
+  mStreamList->NoteClosedAll();
+  mStreamList = nullptr;
+}
+
+bool
+CacheStreamControlParent::RecvNoteClosed(const nsID& aId)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
+  MOZ_ASSERT(mStreamList);
+  mStreamList->NoteClosed(aId);
+  return true;
+}
+
+void
+CacheStreamControlParent::SetStreamList(StreamList* aStreamList)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
+  MOZ_ASSERT(!mStreamList);
+  mStreamList = aStreamList;
+}
+
+void
+CacheStreamControlParent::Close(const nsID& aId)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
+  NotifyClose(aId);
+  unused << SendClose(aId);
+}
+
+void
+CacheStreamControlParent::CloseAll()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
+  NotifyCloseAll();
+  unused << SendCloseAll();
+}
+
+void
+CacheStreamControlParent::Shutdown()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
+  if (!Send__delete__(this)) {
+    // child process is gone, allow actor to be destroyed normally
+    NS_WARNING("Cache failed to delete stream actor.");
+    return;
+  }
+}
+
+void
+CacheStreamControlParent::NotifyClose(const nsID& aId)
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
+  DebugOnly<uint32_t> closedCount = 0;
+
+  ReadStreamList::ForwardIterator iter(mListeners);
+  while (iter.HasMore()) {
+    nsRefPtr<ReadStream> stream = iter.GetNext();
+    // note, multiple streams may exist for same ID
+    if (stream->MatchId(aId)) {
+      stream->CloseStream();
+      closedCount += 1;
+    }
+  }
+
+  MOZ_ASSERT(closedCount > 0);
+}
+
+void
+CacheStreamControlParent::NotifyCloseAll()
+{
+  NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent);
+  ReadStreamList::ForwardIterator iter(mListeners);
+  while (iter.HasMore()) {
+    nsRefPtr<ReadStream> stream = iter.GetNext();
+    stream->CloseStream();
+  }
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/CacheStreamControlParent.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_CacheStreamControlParent_h
+#define mozilla_dom_cache_CacheStreamControlParent_h
+
+#include "mozilla/dom/cache/PCacheStreamControlParent.h"
+#include "nsTObserverArray.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class ReadStream;
+class StreamList;
+
+class CacheStreamControlParent : public PCacheStreamControlParent
+{
+public:
+  CacheStreamControlParent();
+  ~CacheStreamControlParent();
+
+  void AddListener(ReadStream* aListener);
+  void RemoveListener(ReadStream* aListener);
+
+  void SetStreamList(StreamList* aStreamList);
+  void Close(const nsID& aId);
+  void CloseAll();
+  void Shutdown();
+
+  // PCacheStreamControlParent methods
+  virtual void ActorDestroy(ActorDestroyReason aReason) MOZ_OVERRIDE;
+  virtual bool RecvNoteClosed(const nsID& aId) MOZ_OVERRIDE;
+
+private:
+  void NotifyClose(const nsID& aId);
+  void NotifyCloseAll();
+
+  // Cycle with StreamList via a weak-ref to us.  Cleanup occurs when the actor
+  // is deleted by the PBackground manager.  ActorDestroy() then calls
+  // StreamList::RemoveStreamControl() to clear the weak ref.
+  nsRefPtr<StreamList> mStreamList;
+
+  typedef nsTObserverArray<ReadStream*> ReadStreamList;
+  ReadStreamList mListeners;
+
+  NS_DECL_OWNINGTHREAD
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_CacheStreamControlParent_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/Context.cpp
@@ -0,0 +1,630 @@
+/* -*- 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/Context.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/cache/Action.h"
+#include "mozilla/dom/cache/Manager.h"
+#include "mozilla/dom/cache/ManagerId.h"
+#include "mozilla/dom/quota/OriginOrPatternString.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "nsIFile.h"
+#include "nsIPrincipal.h"
+#include "nsIRunnable.h"
+#include "nsThreadUtils.h"
+
+namespace {
+
+using mozilla::dom::Nullable;
+using mozilla::dom::cache::QuotaInfo;
+using mozilla::dom::quota::OriginOrPatternString;
+using mozilla::dom::quota::QuotaManager;
+using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
+using mozilla::dom::quota::PersistenceType;
+
+// Executed when the context is destroyed to release our lock on the
+// QuotaManager.
+class QuotaReleaseRunnable MOZ_FINAL : public nsRunnable
+{
+public:
+  QuotaReleaseRunnable(const QuotaInfo& aQuotaInfo, const nsACString& aQuotaId)
+    : mQuotaInfo(aQuotaInfo)
+    , mQuotaId(aQuotaId)
+  { }
+
+  NS_IMETHOD Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    QuotaManager* qm = QuotaManager::Get();
+    MOZ_ASSERT(qm);
+    qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin),
+                                Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT),
+                                mQuotaId);
+    return NS_OK;
+  }
+
+private:
+  ~QuotaReleaseRunnable() { }
+
+  const QuotaInfo mQuotaInfo;
+  const nsCString mQuotaId;
+};
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+using mozilla::DebugOnly;
+using mozilla::dom::quota::OriginOrPatternString;
+using mozilla::dom::quota::QuotaManager;
+using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
+using mozilla::dom::quota::PersistenceType;
+
+// Executed to perform the complicated dance of steps necessary to initialize
+// the QuotaManager.  This must be performed for each origin before any disk
+// IO occurrs.
+class Context::QuotaInitRunnable MOZ_FINAL : public nsIRunnable
+                                           , public Action::Resolver
+{
+public:
+  QuotaInitRunnable(Context* aContext,
+                    Manager* aManager,
+                    const nsACString& aQuotaId,
+                    Action* aQuotaIOThreadAction)
+    : mContext(aContext)
+    , mManager(aManager)
+    , mQuotaId(aQuotaId)
+    , mQuotaIOThreadAction(aQuotaIOThreadAction)
+    , mInitiatingThread(NS_GetCurrentThread())
+    , mState(STATE_INIT)
+    , mResult(NS_OK)
+  {
+    MOZ_ASSERT(mContext);
+    MOZ_ASSERT(mManager);
+    MOZ_ASSERT(mInitiatingThread);
+  }
+
+  nsresult Dispatch()
+  {
+    NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+    MOZ_ASSERT(mState == STATE_INIT);
+
+    mState = STATE_CALL_WAIT_FOR_OPEN_ALLOWED;
+    nsresult rv = NS_DispatchToMainThread(this, nsIThread::DISPATCH_NORMAL);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mState = STATE_COMPLETE;
+      Clear();
+    }
+    return rv;
+  }
+
+  virtual void Resolve(nsresult aRv) MOZ_OVERRIDE
+  {
+    // Depending on the error or success path, this can run on either the
+    // main thread or the QuotaManager IO thread.  The IO thread is an
+    // idle thread which may be destroyed and recreated, so its hard to
+    // assert on.
+    MOZ_ASSERT(mState == STATE_RUNNING || NS_FAILED(aRv));
+
+    mResult = aRv;
+    mState = STATE_COMPLETING;
+
+    nsresult rv = mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL);
+    if (NS_FAILED(rv)) {
+      // Shutdown must be delayed until all Contexts are destroyed.  Crash for
+      // this invariant violation.
+      MOZ_CRASH("Failed to dispatch QuotaInitRunnable to initiating thread.");
+    }
+  }
+
+private:
+  ~QuotaInitRunnable()
+  {
+    MOZ_ASSERT(mState == STATE_COMPLETE);
+    MOZ_ASSERT(!mContext);
+    MOZ_ASSERT(!mQuotaIOThreadAction);
+  }
+
+  enum State
+  {
+    STATE_INIT,
+    STATE_CALL_WAIT_FOR_OPEN_ALLOWED,
+    STATE_WAIT_FOR_OPEN_ALLOWED,
+    STATE_ENSURE_ORIGIN_INITIALIZED,
+    STATE_RUNNING,
+    STATE_COMPLETING,
+    STATE_COMPLETE
+  };
+
+  void Clear()
+  {
+    NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+    MOZ_ASSERT(mContext);
+    mContext = nullptr;
+    mManager = nullptr;
+    mQuotaIOThreadAction = nullptr;
+  }
+
+  nsRefPtr<Context> mContext;
+  nsRefPtr<Manager> mManager;
+  const nsCString mQuotaId;
+  nsRefPtr<Action> mQuotaIOThreadAction;
+  nsCOMPtr<nsIThread> mInitiatingThread;
+  State mState;
+  nsresult mResult;
+  QuotaInfo mQuotaInfo;
+
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIRUNNABLE
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(mozilla::dom::cache::Context::QuotaInitRunnable,
+                            Action::Resolver, nsIRunnable);
+
+// The QuotaManager init state machine is represented in the following diagram:
+//
+//    +---------------+
+//    |     Start     |      Resolve(error)
+//    | (Orig Thread) +---------------------+
+//    +-------+-------+                     |
+//            |                             |
+// +----------v-----------+                 |
+// |CallWaitForOpenAllowed|  Resolve(error) |
+// |    (Main Thread)     +-----------------+
+// +----------+-----------+                 |
+//            |                             |
+//   +--------v---------+                   |
+//   |WaitForOpenAllowed|    Resolve(error) |
+//   |  (Main Thread)   +-------------------+
+//   +--------+---------+                   |
+//            |                             |
+// +----------v------------+                |
+// |EnsureOriginInitialized| Resolve(error) |
+// |   (Quota IO Thread)   +----------------+
+// +----------+------------+                |
+//            |                             |
+//  +---------v---------+            +------v------+
+//  |      Running      |  Resolve() |  Completing |
+//  | (Quota IO Thread) +------------>(Orig Thread)|
+//  +-------------------+            +------+------+
+//                                          |
+//                                    +-----v----+
+//                                    | Complete |
+//                                    +----------+
+//
+// The initialization process proceeds through the main states.  If an error
+// occurs, then we transition back to Completing state back on the original
+// thread.
+NS_IMETHODIMP
+Context::QuotaInitRunnable::Run()
+{
+  // May run on different threads depending on the state.  See individual
+  // state cases for thread assertions.
+
+  switch(mState) {
+    // -----------------------------------
+    case STATE_CALL_WAIT_FOR_OPEN_ALLOWED:
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+      QuotaManager* qm = QuotaManager::GetOrCreate();
+      if (!qm) {
+        Resolve(NS_ERROR_FAILURE);
+        return NS_OK;
+      }
+
+      nsRefPtr<ManagerId> managerId = mManager->GetManagerId();
+      nsCOMPtr<nsIPrincipal> principal = managerId->Principal();
+      nsresult rv = qm->GetInfoFromPrincipal(principal,
+                                             &mQuotaInfo.mGroup,
+                                             &mQuotaInfo.mOrigin,
+                                             &mQuotaInfo.mIsApp);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        Resolve(rv);
+        return NS_OK;
+      }
+
+      // QuotaManager::WaitForOpenAllowed() will hold a reference to us as
+      // a callback.  We will then get executed again on the main thread when
+      // it is safe to open the quota directory.
+      mState = STATE_WAIT_FOR_OPEN_ALLOWED;
+      rv = qm->WaitForOpenAllowed(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin),
+                                  Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT),
+                                  mQuotaId, this);
+      if (NS_FAILED(rv)) {
+        Resolve(rv);
+        return NS_OK;
+      }
+      break;
+    }
+    // ------------------------------
+    case STATE_WAIT_FOR_OPEN_ALLOWED:
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+      QuotaManager* qm = QuotaManager::Get();
+      MOZ_ASSERT(qm);
+      mState = STATE_ENSURE_ORIGIN_INITIALIZED;
+      nsresult rv = qm->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        Resolve(rv);
+        return NS_OK;
+      }
+      break;
+    }
+    // ----------------------------------
+    case STATE_ENSURE_ORIGIN_INITIALIZED:
+    {
+      // Can't assert quota IO thread because its an idle thread that can get
+      // recreated.  At least assert we're not on main thread or owning thread.
+      MOZ_ASSERT(!NS_IsMainThread());
+      MOZ_ASSERT(_mOwningThread.GetThread() != PR_GetCurrentThread());
+
+      QuotaManager* qm = QuotaManager::Get();
+      MOZ_ASSERT(qm);
+      nsresult rv = qm->EnsureOriginIsInitialized(PERSISTENCE_TYPE_DEFAULT,
+                                                  mQuotaInfo.mGroup,
+                                                  mQuotaInfo.mOrigin,
+                                                  mQuotaInfo.mIsApp,
+                                                  getter_AddRefs(mQuotaInfo.mDir));
+      if (NS_FAILED(rv)) {
+        Resolve(rv);
+        return NS_OK;
+      }
+
+      mState = STATE_RUNNING;
+
+      if (!mQuotaIOThreadAction) {
+        Resolve(NS_OK);
+        return NS_OK;
+      }
+
+      // Execute the provided initialization Action.  We pass ourselves as the
+      // Resolver.  The Action must either call Resolve() immediately or hold
+      // a ref to us and call Resolve() later.
+      mQuotaIOThreadAction->RunOnTarget(this, mQuotaInfo);
+
+      break;
+    }
+    // -------------------
+    case STATE_COMPLETING:
+    {
+      NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+      if (mQuotaIOThreadAction) {
+        mQuotaIOThreadAction->CompleteOnInitiatingThread(mResult);
+      }
+      mContext->OnQuotaInit(mResult, mQuotaInfo);
+      mState = STATE_COMPLETE;
+      // Explicitly cleanup here as the destructor could fire on any of
+      // the threads we have bounced through.
+      Clear();
+      break;
+    }
+    // -----
+    default:
+    {
+      MOZ_CRASH("unexpected state in QuotaInitRunnable");
+      break;
+    }
+  }
+
+  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 MOZ_FINAL : public nsIRunnable
+                                        , public Action::Resolver
+{
+public:
+  ActionRunnable(Context* aContext, nsIEventTarget* aTarget, Action* aAction,
+                 const QuotaInfo& aQuotaInfo)
+    : mContext(aContext)
+    , mTarget(aTarget)
+    , mAction(aAction)
+    , mQuotaInfo(aQuotaInfo)
+    , mInitiatingThread(NS_GetCurrentThread())
+    , mState(STATE_INIT)
+    , mResult(NS_OK)
+  {
+    MOZ_ASSERT(mContext);
+    MOZ_ASSERT(mTarget);
+    MOZ_ASSERT(mAction);
+    MOZ_ASSERT(mQuotaInfo.mDir);
+    MOZ_ASSERT(mInitiatingThread);
+  }
+
+  nsresult Dispatch()
+  {
+    NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+    MOZ_ASSERT(mState == STATE_INIT);
+
+    mState = STATE_RUN_ON_TARGET;
+    nsresult rv = mTarget->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mState = STATE_COMPLETE;
+      Clear();
+    }
+    return rv;
+  }
+
+  bool MatchesCacheId(CacheId aCacheId) {
+    NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+    return mAction->MatchesCacheId(aCacheId);
+  }
+
+  void Cancel()
+  {
+    NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+    mAction->CancelOnInitiatingThread();
+  }
+
+  virtual void Resolve(nsresult aRv) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(mTarget == NS_GetCurrentThread());
+    MOZ_ASSERT(mState == STATE_RUNNING);
+    mResult = aRv;
+    mState = STATE_COMPLETING;
+    nsresult rv = mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL);
+    if (NS_FAILED(rv)) {
+      // Shutdown must be delayed until all Contexts are destroyed.  Crash
+      // for this invariant violation.
+      MOZ_CRASH("Failed to dispatch ActionRunnable to initiating thread.");
+    }
+  }
+
+private:
+  ~ActionRunnable()
+  {
+    MOZ_ASSERT(mState == STATE_COMPLETE);
+    MOZ_ASSERT(!mContext);
+    MOZ_ASSERT(!mAction);
+  }
+
+  void Clear()
+  {
+    NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+    MOZ_ASSERT(mContext);
+    MOZ_ASSERT(mAction);
+    mContext->OnActionRunnableComplete(this);
+    mContext = nullptr;
+    mAction = nullptr;
+  }
+
+  enum State
+  {
+    STATE_INIT,
+    STATE_RUN_ON_TARGET,
+    STATE_RUNNING,
+    STATE_COMPLETING,
+    STATE_COMPLETE
+  };
+
+  nsRefPtr<Context> mContext;
+  nsCOMPtr<nsIEventTarget> mTarget;
+  nsRefPtr<Action> mAction;
+  const QuotaInfo mQuotaInfo;
+  nsCOMPtr<nsIThread> mInitiatingThread;
+  State mState;
+  nsresult mResult;
+
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIRUNNABLE
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(mozilla::dom::cache::Context::ActionRunnable,
+                            Action::Resolver, nsIRunnable);
+
+// The ActionRunnable has a simpler state machine.  It basically needs to run
+// the action on the target thread and then complete on the original thread.
+//
+//   +-------------+
+//   |    Start    |
+//   |(Orig Thread)|
+//   +-----+-------+
+//         |
+// +-------v---------+
+// |  RunOnTarget    |
+// |Target IO Thread)+-------------------------------+
+// +-------+---------+                               |
+//         |                                         |
+// +-------v----------+ Resolve()            +-------v-----+
+// |     Running      |                      |  Completing |
+// |(Target IO Thread)+---------------------->(Orig Thread)|
+// +------------------+                      +-------+-----+
+//                                                   |
+//                                                   |
+//                                              +----v---+
+//                                              |Complete|
+//                                              +--------+
+//
+// Its important to note that synchronous actions will effectively Resolve()
+// out of the Running state immediately.  Asynchronous Actions may remain
+// in the Running state for some time, but normally the ActionRunnable itself
+// does not see any execution there.  Its all handled internal to the Action.
+NS_IMETHODIMP
+Context::ActionRunnable::Run()
+{
+  switch(mState) {
+    // ----------------------
+    case STATE_RUN_ON_TARGET:
+    {
+      MOZ_ASSERT(NS_GetCurrentThread() == mTarget);
+      mState = STATE_RUNNING;
+      mAction->RunOnTarget(this, mQuotaInfo);
+      break;
+    }
+    // -------------------
+    case STATE_COMPLETING:
+    {
+      NS_ASSERT_OWNINGTHREAD(Action::Resolver);
+      mAction->CompleteOnInitiatingThread(mResult);
+      mState = STATE_COMPLETE;
+      // Explicitly cleanup here as the destructor could fire on any of
+      // the threads we have bounced through.
+      Clear();
+      break;
+    }
+    // -----------------
+    default:
+    {
+      MOZ_CRASH("unexpected state in ActionRunnable");
+      break;
+    }
+  }
+  return NS_OK;
+}
+
+// static
+already_AddRefed<Context>
+Context::Create(Manager* aManager, Action* aQuotaIOThreadAction)
+{
+  nsRefPtr<Context> context = new Context(aManager);
+
+  nsRefPtr<QuotaInitRunnable> runnable =
+    new QuotaInitRunnable(context, aManager, NS_LITERAL_CSTRING("Cache"),
+                          aQuotaIOThreadAction);
+  nsresult rv = runnable->Dispatch();
+  if (NS_FAILED(rv)) {
+    // Shutdown must be delayed until all Contexts are destroyed.  Shutdown
+    // must also prevent any new Contexts from being constructed.  Crash
+    // for this invariant violation.
+    MOZ_CRASH("Failed to dispatch QuotaInitRunnable.");
+  }
+
+  return context.forget();
+}
+
+Context::Context(Manager* aManager)
+  : mManager(aManager)
+  , mState(STATE_CONTEXT_INIT)
+{
+  MOZ_ASSERT(mManager);
+}
+
+void
+Context::Dispatch(nsIEventTarget* aTarget, Action* aAction)
+{
+  NS_ASSERT_OWNINGTHREAD(Context);
+  MOZ_ASSERT(aTarget);
+  MOZ_ASSERT(aAction);
+
+  if (mState == STATE_CONTEXT_CANCELED) {
+    return;
+  } else if (mState == STATE_CONTEXT_INIT) {
+    PendingAction* pending = mPendingActions.AppendElement();
+    pending->mTarget = aTarget;
+    pending->mAction = aAction;
+    return;
+  }
+
+  MOZ_ASSERT(STATE_CONTEXT_READY);
+  DispatchAction(aTarget, aAction);
+}
+
+void
+Context::CancelAll()
+{
+  NS_ASSERT_OWNINGTHREAD(Context);
+  mState = STATE_CONTEXT_CANCELED;
+  mPendingActions.Clear();
+  for (uint32_t i = 0; i < mActionRunnables.Length(); ++i) {
+    nsRefPtr<ActionRunnable> runnable = mActionRunnables[i];
+    runnable->Cancel();
+  }
+}
+
+void
+Context::CancelForCacheId(CacheId aCacheId)
+{
+  NS_ASSERT_OWNINGTHREAD(Context);
+  for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
+    if (mPendingActions[i].mAction->MatchesCacheId(aCacheId)) {
+      mPendingActions.RemoveElementAt(i);
+    }
+  }
+  for (uint32_t i = 0; i < mActionRunnables.Length(); ++i) {
+    nsRefPtr<ActionRunnable> runnable = mActionRunnables[i];
+    if (runnable->MatchesCacheId(aCacheId)) {
+      runnable->Cancel();
+    }
+  }
+}
+
+Context::~Context()
+{
+  NS_ASSERT_OWNINGTHREAD(Context);
+  MOZ_ASSERT(mManager);
+
+  // Unlock the quota dir as we go out of scope.
+  nsCOMPtr<nsIRunnable> runnable =
+    new QuotaReleaseRunnable(mQuotaInfo, NS_LITERAL_CSTRING("Cache"));
+  nsresult rv = NS_DispatchToMainThread(runnable, nsIThread::DISPATCH_NORMAL);
+  if (NS_FAILED(rv)) {
+    // Shutdown must be delayed until all Contexts are destroyed.  Crash
+    // for this invariant violation.
+    MOZ_CRASH("Failed to dispatch QuotaReleaseRunnable to main thread.");
+  }
+
+  mManager->RemoveContext(this);
+}
+
+void
+Context::DispatchAction(nsIEventTarget* aTarget, Action* aAction)
+{
+  NS_ASSERT_OWNINGTHREAD(Context);
+
+  nsRefPtr<ActionRunnable> runnable =
+    new ActionRunnable(this, aTarget, aAction, mQuotaInfo);
+  nsresult rv = runnable->Dispatch();
+  if (NS_FAILED(rv)) {
+    // Shutdown must be delayed until all Contexts are destroyed.  Crash
+    // for this invariant violation.
+    MOZ_CRASH("Failed to dispatch ActionRunnable to target thread.");
+  }
+  mActionRunnables.AppendElement(runnable);
+}
+
+void
+Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo)
+{
+  NS_ASSERT_OWNINGTHREAD(Context);
+
+  mQuotaInfo = aQuotaInfo;
+
+  if (mState == STATE_CONTEXT_CANCELED || NS_FAILED(aRv)) {
+    for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
+      mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv);
+    }
+    mPendingActions.Clear();
+    // Context will destruct after return here and last ref is released.
+    return;
+  }
+
+  MOZ_ASSERT(mState == STATE_CONTEXT_INIT);
+  mState = STATE_CONTEXT_READY;
+
+  for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
+    DispatchAction(mPendingActions[i].mTarget, mPendingActions[i].mAction);
+  }
+  mPendingActions.Clear();
+}
+
+void
+Context::OnActionRunnableComplete(ActionRunnable* aActionRunnable)
+{
+  NS_ASSERT_OWNINGTHREAD(Context);
+  MOZ_ASSERT(aActionRunnable);
+  MOZ_ALWAYS_TRUE(mActionRunnables.RemoveElement(aActionRunnable));
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/Context.h
@@ -0,0 +1,109 @@
+/* -*- 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_Context_h
+#define mozilla_dom_cache_Context_h
+
+#include "mozilla/dom/cache/Types.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsIEventTarget;
+class nsIFile;
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class Action;
+class Manager;
+
+// The Context class is RAII-style class for managing IO operations within the
+// Cache.
+//
+// When a Context is created it performs the complicated steps necessary to
+// initialize the QuotaManager.  Action objects dispatched on the Context are
+// delayed until this initialization is complete.  They are then allow to
+// execute on any specified thread.  Once all references to the Context are
+// gone, then the steps necessary to release the QuotaManager are performed.
+// Since pending Action objects reference the Context, this allows overlapping
+// IO to opportunistically run without re-initializing the QuotaManager again.
+//
+// While the Context performs operations asynchronously on threads, all of
+// methods in its public interface must be called on the same thread
+// originally used to create the Context.
+//
+// As an invariant, all Context objects must be destroyed before permitting
+// the "profile-before-change" shutdown event to complete.  This is ensured
+// via the code in ShutdownObserver.cpp.
+class Context MOZ_FINAL
+{
+public:
+  static already_AddRefed<Context>
+  Create(Manager* aManager, Action* aQuotaIOThreadAction);
+
+  // Execute given action on the target once the quota manager has been
+  // initialized.
+  //
+  // Only callable from the thread that created the Context.
+  void Dispatch(nsIEventTarget* aTarget, Action* aAction);
+
+  // Cancel any Actions running or waiting to run.  This should allow the
+  // Context to be released and Listener::RemoveContext() will be called
+  // when complete.
+  //
+  // Only callable from the thread that created the Context.
+  void CancelAll();
+
+  // Cancel any Actions running or waiting to run that operate on the given
+  // cache ID.
+  //
+  // Only callable from the thread that created the Context.
+  void CancelForCacheId(CacheId aCacheId);
+
+private:
+  class QuotaInitRunnable;
+  class ActionRunnable;
+
+  enum State
+  {
+    STATE_CONTEXT_INIT,
+    STATE_CONTEXT_READY,
+    STATE_CONTEXT_CANCELED
+  };
+
+  struct PendingAction
+  {
+    nsCOMPtr<nsIEventTarget> mTarget;
+    nsRefPtr<Action> mAction;
+  };
+
+  explicit Context(Manager* aManager);
+  ~Context();
+  void DispatchAction(nsIEventTarget* aTarget, Action* aAction);
+  void OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo);
+  void OnActionRunnableComplete(ActionRunnable* const aAction);
+
+  nsRefPtr<Manager> mManager;
+  State mState;
+  QuotaInfo mQuotaInfo;
+  nsTArray<PendingAction> mPendingActions;
+
+  // weak refs since ~ActionRunnable() removes itself from this list
+  nsTArray<ActionRunnable*> mActionRunnables;
+
+public:
+  NS_INLINE_DECL_REFCOUNTING(cache::Context)
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_Context_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/DBAction.cpp
@@ -0,0 +1,169 @@
+/* -*- 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/DBAction.h"
+
+#include "mozilla/dom/quota/PersistenceType.h"
+#include "mozilla/net/nsFileProtocolHandler.h"
+#include "mozIStorageConnection.h"
+#include "mozIStorageService.h"
+#include "mozStorageCID.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
+using mozilla::dom::quota::PersistenceType;
+
+DBAction::DBAction(Mode aMode)
+  : mMode(aMode)
+{
+}
+
+DBAction::~DBAction()
+{
+}
+
+void
+DBAction::RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aResolver);
+  MOZ_ASSERT(aQuotaInfo.mDir);
+
+  if (IsCanceled()) {
+    aResolver->Resolve(NS_ERROR_ABORT);
+    return;
+  }
+
+  nsCOMPtr<nsIFile> dbDir;
+  nsresult rv = aQuotaInfo.mDir->Clone(getter_AddRefs(dbDir));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aResolver->Resolve(rv);
+    return;
+  }
+
+  rv = dbDir->Append(NS_LITERAL_STRING("cache"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aResolver->Resolve(rv);
+    return;
+  }
+
+  nsCOMPtr<mozIStorageConnection> conn;
+  rv = OpenConnection(aQuotaInfo, dbDir, getter_AddRefs(conn));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aResolver->Resolve(rv);
+    return;
+  }
+  MOZ_ASSERT(conn);
+
+  RunWithDBOnTarget(aResolver, aQuotaInfo, dbDir, conn);
+}
+
+nsresult
+DBAction::OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                         mozIStorageConnection** aConnOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aDBDir);
+  MOZ_ASSERT(aConnOut);
+
+  bool exists;
+  nsresult rv = aDBDir->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  if (!exists) {
+    if (NS_WARN_IF(mMode != Create)) {  return NS_ERROR_FILE_NOT_FOUND; }
+    rv = aDBDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  }
+
+  nsCOMPtr<nsIFile> dbFile;
+  rv = aDBDir->Clone(getter_AddRefs(dbFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = dbFile->Append(NS_LITERAL_STRING("caches.sqlite"));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = dbFile->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  // Use our default file:// protocol handler directly to construct the database
+  // URL.  This avoids any problems if a plugin registers a custom file://
+  // handler.  If such a custom handler used javascript, then we would have a
+  // bad time running off the main thread here.
+  nsRefPtr<nsFileProtocolHandler> handler = new nsFileProtocolHandler();
+  rv = handler->Init();
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  nsCOMPtr<nsIURI> uri;
+  rv = handler->NewFileURI(dbFile, getter_AddRefs(uri));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  nsCOMPtr<nsIFileURL> dbFileUrl = do_QueryInterface(uri);
+  if (NS_WARN_IF(!dbFileUrl)) { return NS_ERROR_UNEXPECTED; }
+
+  nsAutoCString type;
+  PersistenceTypeToText(PERSISTENCE_TYPE_DEFAULT, type);
+
+  rv = dbFileUrl->SetQuery(
+    NS_LITERAL_CSTRING("persistenceType=") + type +
+    NS_LITERAL_CSTRING("&group=") + aQuotaInfo.mGroup +
+    NS_LITERAL_CSTRING("&origin=") + aQuotaInfo.mOrigin);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  nsCOMPtr<mozIStorageService> ss =
+    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!ss)) { return NS_ERROR_UNEXPECTED; }
+
+  rv = ss->OpenDatabaseWithFileURL(dbFileUrl, aConnOut);
+  if (rv == NS_ERROR_FILE_CORRUPTED) {
+    NS_WARNING("Cache database corrupted. Recreating empty database.");
+
+    // There is nothing else we can do to recover.  Also, this data can
+    // be deleted by QuotaManager at any time anyways.
+    rv = dbFile->Remove(false);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    // TODO: clean up any orphaned body files (bug 1110446)
+
+    rv = ss->OpenDatabaseWithFileURL(dbFileUrl, aConnOut);
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  MOZ_ASSERT(*aConnOut);
+  return rv;
+}
+
+SyncDBAction::SyncDBAction(Mode aMode)
+  : DBAction(aMode)
+{
+}
+
+SyncDBAction::~SyncDBAction()
+{
+}
+
+void
+SyncDBAction::RunWithDBOnTarget(Resolver* aResolver,
+                                const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                                mozIStorageConnection* aConn)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aResolver);
+  MOZ_ASSERT(aDBDir);
+  MOZ_ASSERT(aConn);
+
+  nsresult rv = RunSyncWithDBOnTarget(aQuotaInfo, aDBDir, aConn);
+  aResolver->Resolve(rv);
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/DBAction.h
@@ -0,0 +1,77 @@
+/* -*- 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_DBAction_h
+#define mozilla_dom_cache_DBAction_h
+
+#include "mozilla/dom/cache/Action.h"
+#include "mozilla/dom/cache/CacheInitData.h"
+#include "nsRefPtr.h"
+#include "nsString.h"
+
+class mozIStorageConnection;
+class nsIFile;
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class DBAction : public Action
+{
+protected:
+  // The mode specifies whether the database should already exist or if its
+  // ok to create a new database.
+  enum Mode
+  {
+    Existing,
+    Create
+  };
+
+  explicit DBAction(Mode aMode);
+
+  // Action objects are deleted through their base pointer
+  virtual ~DBAction();
+
+  // Just as the resolver must be ref'd until resolve, you may also
+  // ref the DB connection.  The connection can only be referenced from the
+  // target thread and must be released upon resolve.
+  virtual void
+  RunWithDBOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo,
+                    nsIFile* aDBDir, mozIStorageConnection* aConn) = 0;
+
+private:
+  virtual void
+  RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo) MOZ_OVERRIDE;
+
+  nsresult OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aQuotaDir,
+                          mozIStorageConnection** aConnOut);
+
+  const Mode mMode;
+};
+
+class SyncDBAction : public DBAction
+{
+protected:
+  explicit SyncDBAction(Mode aMode);
+
+  // Action objects are deleted through their base pointer
+  virtual ~SyncDBAction();
+
+  virtual nsresult
+  RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                        mozIStorageConnection* aConn) = 0;
+
+private:
+  virtual void
+  RunWithDBOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo,
+                    nsIFile* aDBDir, mozIStorageConnection* aConn) MOZ_OVERRIDE;
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_DBAction_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/DBSchema.cpp
@@ -0,0 +1,1322 @@
+/* -*- 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/DBSchema.h"
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/dom/InternalHeaders.h"
+#include "mozilla/dom/cache/PCacheTypes.h"
+#include "mozilla/dom/cache/SavedTypes.h"
+#include "mozIStorageConnection.h"
+#include "mozIStorageStatement.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+
+const int32_t DBSchema::kLatestSchemaVersion = 1;
+const int32_t DBSchema::kMaxEntriesPerStatement = 255;
+
+using mozilla::void_t;
+
+// static
+nsresult
+DBSchema::CreateSchema(mozIStorageConnection* aConn)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  nsAutoCString pragmas(
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+    // Switch the journaling mode to TRUNCATE to avoid changing the directory
+    // structure at the conclusion of every transaction for devices with slower
+    // file systems.
+    "PRAGMA journal_mode = TRUNCATE; "
+#endif
+    "PRAGMA foreign_keys = ON; "
+
+    // Note, the default encoding of UTF-8 is preferred.  mozStorage does all
+    // the work necessary to convert UTF-16 nsString values for us.  We don't
+    // need ordering and the binary equality operations are correct.  So, do
+    // NOT set PRAGMA encoding to UTF-16.
+  );
+
+  nsresult rv = aConn->ExecuteSimpleSQL(pragmas);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  int32_t schemaVersion;
+  rv = aConn->GetSchemaVersion(&schemaVersion);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  if (schemaVersion == kLatestSchemaVersion) {
+    return rv;
+  }
+
+  if (!schemaVersion) {
+    // The caches table is the single source of truth about what Cache
+    // objects exist for the origin.  The contents of the Cache are stored
+    // in the entries table that references back to caches.
+    //
+    // The caches table is also referenced from storage.  Rows in storage
+    // represent named Cache objects.  There are cases, however, where
+    // a Cache can still exist, but not be in a named Storage.  For example,
+    // when content is still using the Cache after CacheStorage::Delete()
+    // has been run.
+    //
+    // For now, the caches table mainly exists for data integrity with
+    // foreign keys, but could be expanded to contain additional cache object
+    // information.
+    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "CREATE TABLE caches ("
+        "id INTEGER NOT NULL PRIMARY KEY "
+      ");"
+    ));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "CREATE TABLE entries ("
+        "id INTEGER NOT NULL PRIMARY KEY, "
+        "request_method TEXT NOT NULL, "
+        "request_url TEXT NOT NULL, "
+        "request_url_no_query TEXT NOT NULL, "
+        "request_referrer TEXT NOT NULL, "
+        "request_headers_guard INTEGER NOT NULL, "
+        "request_mode INTEGER NOT NULL, "
+        "request_credentials INTEGER NOT NULL, "
+        "request_body_id TEXT NULL, "
+        "response_type INTEGER NOT NULL, "
+        "response_url TEXT NOT NULL, "
+        "response_status INTEGER NOT NULL, "
+        "response_status_text TEXT NOT NULL, "
+        "response_headers_guard INTEGER NOT NULL, "
+        "response_body_id TEXT NULL, "
+        "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE"
+      ");"
+    ));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    // TODO: see if we can remove these indices on TEXT columns (bug 1110458)
+    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "CREATE INDEX entries_request_url_index "
+                "ON entries (request_url);"
+    ));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "CREATE INDEX entries_request_url_no_query_index "
+                "ON entries (request_url_no_query);"
+    ));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "CREATE TABLE request_headers ("
+        "name TEXT NOT NULL, "
+        "value TEXT NOT NULL, "
+        "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
+      ");"
+    ));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "CREATE TABLE response_headers ("
+        "name TEXT NOT NULL, "
+        "value TEXT NOT NULL, "
+        "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
+      ");"
+    ));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    // We need an index on response_headers, but not on request_headers,
+    // because we quickly need to determine if a VARY header is present.
+    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "CREATE INDEX response_headers_name_index "
+                "ON response_headers (name);"
+    ));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "CREATE TABLE storage ("
+        "namespace INTEGER NOT NULL, "
+        "key TEXT NOT NULL, "
+        "cache_id INTEGER NOT NULL REFERENCES caches(id), "
+        "PRIMARY KEY(namespace, key) "
+      ");"
+    ));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = aConn->SetSchemaVersion(kLatestSchemaVersion);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = aConn->GetSchemaVersion(&schemaVersion);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  }
+
+  if (schemaVersion != kLatestSchemaVersion) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::CreateCache(mozIStorageConnection* aConn, CacheId* aCacheIdOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+  MOZ_ASSERT(aCacheIdOut);
+
+  nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT INTO caches DEFAULT VALUES;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  nsCOMPtr<mozIStorageStatement> state;
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT last_insert_rowid()"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool hasMoreData = false;
+  rv = state->ExecuteStep(&hasMoreData);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  if (NS_WARN_IF(!hasMoreData)) { return NS_ERROR_UNEXPECTED; }
+
+  rv = state->GetInt32(0, aCacheIdOut);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::DeleteCache(mozIStorageConnection* aConn, CacheId aCacheId,
+                      nsTArray<nsID>& aDeletedBodyIdListOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  // Delete the bodies explicitly as we need to read out the body IDs
+  // anyway.  These body IDs must be deleted one-by-one as content may
+  // still be referencing them invidivually.
+  nsAutoTArray<EntryId, 256> matches;
+  nsresult rv = QueryAll(aConn, aCacheId, matches);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = DeleteEntries(aConn, matches, aDeletedBodyIdListOut);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  // Delete the remainder of the cache using cascade semantics.
+  nsCOMPtr<mozIStorageStatement> state;
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "DELETE FROM caches WHERE id=?1;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, aCacheId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::IsCacheOrphaned(mozIStorageConnection* aConn,
+                          CacheId aCacheId, bool* aOrphanedOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+  MOZ_ASSERT(aOrphanedOut);
+
+  // err on the side of not deleting user data
+  *aOrphanedOut = false;
+
+  nsCOMPtr<mozIStorageStatement> state;
+  nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT COUNT(*) FROM storage WHERE cache_id=?1;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, aCacheId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool hasMoreData = false;
+  rv = state->ExecuteStep(&hasMoreData);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  MOZ_ASSERT(hasMoreData);
+
+  int32_t refCount;
+  rv = state->GetInt32(0, &refCount);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  *aOrphanedOut = refCount == 0;
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::CacheMatch(mozIStorageConnection* aConn, CacheId aCacheId,
+                     const PCacheRequest& aRequest,
+                     const PCacheQueryParams& aParams,
+                     bool* aFoundResponseOut,
+                     SavedResponse* aSavedResponseOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+  MOZ_ASSERT(aFoundResponseOut);
+  MOZ_ASSERT(aSavedResponseOut);
+
+  *aFoundResponseOut = false;
+
+  nsAutoTArray<EntryId, 1> matches;
+  nsresult rv = QueryCache(aConn, aCacheId, aRequest, aParams, matches, 1);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  if (matches.IsEmpty()) {
+    return rv;
+  }
+
+  rv = ReadResponse(aConn, matches[0], aSavedResponseOut);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  aSavedResponseOut->mCacheId = aCacheId;
+  *aFoundResponseOut = true;
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::CacheMatchAll(mozIStorageConnection* aConn, CacheId aCacheId,
+                        const PCacheRequestOrVoid& aRequestOrVoid,
+                        const PCacheQueryParams& aParams,
+                        nsTArray<SavedResponse>& aSavedResponsesOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+  nsresult rv;
+
+  nsAutoTArray<EntryId, 256> matches;
+  if (aRequestOrVoid.type() == PCacheRequestOrVoid::Tvoid_t) {
+    rv = QueryAll(aConn, aCacheId, matches);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  } else {
+    rv = QueryCache(aConn, aCacheId, aRequestOrVoid, aParams, matches);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  }
+
+  // TODO: replace this with a bulk load using SQL IN clause (bug 1110458)
+  for (uint32_t i = 0; i < matches.Length(); ++i) {
+    SavedResponse savedResponse;
+    rv = ReadResponse(aConn, matches[i], &savedResponse);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    savedResponse.mCacheId = aCacheId;
+    aSavedResponsesOut.AppendElement(savedResponse);
+  }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::CachePut(mozIStorageConnection* aConn, CacheId aCacheId,
+                   const PCacheRequest& aRequest,
+                   const nsID* aRequestBodyId,
+                   const PCacheResponse& aResponse,
+                   const nsID* aResponseBodyId,
+                   nsTArray<nsID>& aDeletedBodyIdListOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  PCacheQueryParams params(false, false, false, false, false,
+                           NS_LITERAL_STRING(""));
+  nsAutoTArray<EntryId, 256> matches;
+  nsresult rv = QueryCache(aConn, aCacheId, aRequest, params, matches);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = DeleteEntries(aConn, matches, aDeletedBodyIdListOut);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = InsertEntry(aConn, aCacheId, aRequest, aRequestBodyId, aResponse,
+                   aResponseBodyId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::CacheDelete(mozIStorageConnection* aConn, CacheId aCacheId,
+                      const PCacheRequest& aRequest,
+                      const PCacheQueryParams& aParams,
+                      nsTArray<nsID>& aDeletedBodyIdListOut, bool* aSuccessOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+  MOZ_ASSERT(aSuccessOut);
+
+  *aSuccessOut = false;
+
+  nsAutoTArray<EntryId, 256> matches;
+  nsresult rv = QueryCache(aConn, aCacheId, aRequest, aParams, matches);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  if (matches.IsEmpty()) {
+    return rv;
+  }
+
+  rv = DeleteEntries(aConn, matches, aDeletedBodyIdListOut);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  *aSuccessOut = true;
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::CacheKeys(mozIStorageConnection* aConn, CacheId aCacheId,
+                    const PCacheRequestOrVoid& aRequestOrVoid,
+                    const PCacheQueryParams& aParams,
+                    nsTArray<SavedRequest>& aSavedRequestsOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+  nsresult rv;
+
+  nsAutoTArray<EntryId, 256> matches;
+  if (aRequestOrVoid.type() == PCacheRequestOrVoid::Tvoid_t) {
+    rv = QueryAll(aConn, aCacheId, matches);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  } else {
+    rv = QueryCache(aConn, aCacheId, aRequestOrVoid, aParams, matches);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  }
+
+  // TODO: replace this with a bulk load using SQL IN clause (bug 1110458)
+  for (uint32_t i = 0; i < matches.Length(); ++i) {
+    SavedRequest savedRequest;
+    rv = ReadRequest(aConn, matches[i], &savedRequest);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    savedRequest.mCacheId = aCacheId;
+    aSavedRequestsOut.AppendElement(savedRequest);
+  }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::StorageMatch(mozIStorageConnection* aConn,
+                       Namespace aNamespace,
+                       const PCacheRequest& aRequest,
+                       const PCacheQueryParams& aParams,
+                       bool* aFoundResponseOut,
+                       SavedResponse* aSavedResponseOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+  MOZ_ASSERT(aFoundResponseOut);
+  MOZ_ASSERT(aSavedResponseOut);
+
+  *aFoundResponseOut = false;
+
+  nsresult rv;
+
+  // If we are given a cache to check, then simply find its cache ID
+  // and perform the match.
+  if (!aParams.cacheName().EqualsLiteral("")) {
+    bool foundCache = false;
+    // no invalid CacheId, init to least likely real value
+    CacheId cacheId = INT32_MAX;
+    rv = StorageGetCacheId(aConn, aNamespace, aParams.cacheName(), &foundCache,
+                           &cacheId);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    if (!foundCache) { return NS_ERROR_DOM_NOT_FOUND_ERR; }
+
+    rv = CacheMatch(aConn, cacheId, aRequest, aParams, aFoundResponseOut,
+                    aSavedResponseOut);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    return rv;
+  }
+
+  // Otherwise we need to get a list of all the cache IDs in this namespace.
+
+  nsCOMPtr<mozIStorageStatement> state;
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT cache_id FROM storage WHERE namespace=?1 ORDER BY rowid;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, aNamespace);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  nsAutoTArray<CacheId, 32> cacheIdList;
+
+  bool hasMoreData = false;
+  while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
+    CacheId cacheId = INT32_MAX;
+    rv = state->GetInt32(0, &cacheId);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    cacheIdList.AppendElement(cacheId);
+  }
+
+  // Now try to find a match in each cache in order
+  for (uint32_t i = 0; i < cacheIdList.Length(); ++i) {
+    rv = CacheMatch(aConn, cacheIdList[i], aRequest, aParams, aFoundResponseOut,
+                    aSavedResponseOut);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    if (*aFoundResponseOut) {
+      aSavedResponseOut->mCacheId = cacheIdList[i];
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
+// static
+nsresult
+DBSchema::StorageGetCacheId(mozIStorageConnection* aConn, Namespace aNamespace,
+                            const nsAString& aKey, bool* aFoundCacheOut,
+                            CacheId* aCacheIdOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+  MOZ_ASSERT(aFoundCacheOut);
+  MOZ_ASSERT(aCacheIdOut);
+
+  *aFoundCacheOut = false;
+
+  nsCOMPtr<mozIStorageStatement> state;
+  nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT cache_id FROM storage WHERE namespace=?1 AND key=?2 ORDER BY rowid;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, aNamespace);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindStringParameter(1, aKey);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool hasMoreData = false;
+  rv = state->ExecuteStep(&hasMoreData);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  if (!hasMoreData) {
+    return rv;
+  }
+
+  rv = state->GetInt32(0, aCacheIdOut);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  *aFoundCacheOut = true;
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::StoragePutCache(mozIStorageConnection* aConn, Namespace aNamespace,
+                          const nsAString& aKey, CacheId aCacheId)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  nsCOMPtr<mozIStorageStatement> state;
+  nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "INSERT INTO storage (namespace, key, cache_id) VALUES(?1, ?2, ?3);"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, aNamespace);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindStringParameter(1, aKey);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(2, aCacheId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::StorageForgetCache(mozIStorageConnection* aConn, Namespace aNamespace,
+                             const nsAString& aKey)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  nsCOMPtr<mozIStorageStatement> state;
+  nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "DELETE FROM storage WHERE namespace=?1 AND key=?2;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, aNamespace);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindStringParameter(1, aKey);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::StorageGetKeys(mozIStorageConnection* aConn, Namespace aNamespace,
+                         nsTArray<nsString>& aKeysOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  nsCOMPtr<mozIStorageStatement> state;
+  nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT key FROM storage WHERE namespace=?1 ORDER BY rowid;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, aNamespace);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool hasMoreData = false;
+  while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
+    nsAutoString key;
+    rv = state->GetString(0, key);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    aKeysOut.AppendElement(key);
+  }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::QueryAll(mozIStorageConnection* aConn, CacheId aCacheId,
+                   nsTArray<EntryId>& aEntryIdListOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  nsCOMPtr<mozIStorageStatement> state;
+  nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT id FROM entries WHERE cache_id=?1 ORDER BY id;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, aCacheId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool hasMoreData = false;
+  while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
+    EntryId entryId = INT32_MAX;
+    rv = state->GetInt32(0, &entryId);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    aEntryIdListOut.AppendElement(entryId);
+  }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::QueryCache(mozIStorageConnection* aConn, CacheId aCacheId,
+                     const PCacheRequest& aRequest,
+                     const PCacheQueryParams& aParams,
+                     nsTArray<EntryId>& aEntryIdListOut,
+                     uint32_t aMaxResults)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+  MOZ_ASSERT(aMaxResults > 0);
+
+  if (!aParams.ignoreMethod() && !aRequest.method().LowerCaseEqualsLiteral("get")
+                              && !aRequest.method().LowerCaseEqualsLiteral("head"))
+  {
+    return NS_OK;
+  }
+
+  nsAutoCString query(
+    "SELECT id, COUNT(response_headers.name) AS vary_count "
+    "FROM entries "
+    "LEFT OUTER JOIN response_headers ON entries.id=response_headers.entry_id "
+                                    "AND response_headers.name='vary' "
+    "WHERE entries.cache_id=?1 "
+      "AND entries."
+  );
+
+  nsAutoString urlToMatch;
+  if (aParams.ignoreSearch()) {
+    urlToMatch = aRequest.urlWithoutQuery();
+    query.AppendLiteral("request_url_no_query");
+  } else {
+    urlToMatch = aRequest.url();
+    query.AppendLiteral("request_url");
+  }
+
+  if (aParams.prefixMatch()) {
+    query.AppendLiteral(" LIKE ?2 ESCAPE '\\'");
+  } else {
+    query.AppendLiteral("=?2");
+  }
+
+  query.AppendLiteral(" GROUP BY entries.id ORDER BY entries.id;");
+
+  nsCOMPtr<mozIStorageStatement> state;
+  nsresult rv = aConn->CreateStatement(query, getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, aCacheId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  if (aParams.prefixMatch()) {
+    nsAutoString escapedUrlToMatch;
+    rv = state->EscapeStringForLIKE(urlToMatch, '\\', escapedUrlToMatch);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    urlToMatch = escapedUrlToMatch;
+    urlToMatch.AppendLiteral("%");
+  }
+
+  rv = state->BindStringParameter(1, urlToMatch);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool hasMoreData = false;
+  while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
+    // no invalid EntryId, init to least likely real value
+    EntryId entryId = INT32_MAX;
+    rv = state->GetInt32(0, &entryId);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    int32_t varyCount;
+    rv = state->GetInt32(1, &varyCount);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    if (!aParams.ignoreVary() && varyCount > 0) {
+      bool matchedByVary = false;
+      rv = MatchByVaryHeader(aConn, aRequest, entryId, &matchedByVary);
+      if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+      if (!matchedByVary) {
+        continue;
+      }
+    }
+
+    aEntryIdListOut.AppendElement(entryId);
+
+    if (aEntryIdListOut.Length() == aMaxResults) {
+      return NS_OK;
+    }
+  }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::MatchByVaryHeader(mozIStorageConnection* aConn,
+                            const PCacheRequest& aRequest,
+                            EntryId entryId, bool* aSuccessOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  *aSuccessOut = false;
+
+  nsCOMPtr<mozIStorageStatement> state;
+  nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT value FROM response_headers "
+    "WHERE name='vary' AND entry_id=?1;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, entryId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  nsAutoTArray<nsCString, 8> varyValues;
+
+  bool hasMoreData = false;
+  while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
+    nsAutoCString value;
+    rv = state->GetUTF8String(0, value);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    varyValues.AppendElement(value);
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  // Should not have called this function if this was not the case
+  MOZ_ASSERT(!varyValues.IsEmpty());
+
+  state->Reset();
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT name, value FROM request_headers "
+    "WHERE entry_id=?1;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, entryId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  nsRefPtr<InternalHeaders> cachedHeaders = new InternalHeaders(HeadersGuardEnum::None);
+
+  ErrorResult errorResult;
+
+  while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
+    nsAutoCString name;
+    nsAutoCString value;
+    rv = state->GetUTF8String(0, name);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    rv = state->GetUTF8String(1, value);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    cachedHeaders->Append(name, value, errorResult);
+    if (errorResult.Failed()) { return errorResult.ErrorCode(); };
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  nsRefPtr<InternalHeaders> queryHeaders = new InternalHeaders(aRequest.headers());
+
+  // Assume the vary headers match until we find a conflict
+  bool varyHeadersMatch = true;
+
+  for (uint32_t i = 0; i < varyValues.Length(); ++i) {
+    if (varyValues[i].EqualsLiteral("*")) {
+      continue;
+    }
+
+    nsAutoCString queryValue;
+    queryHeaders->Get(varyValues[i], queryValue, errorResult);
+    if (errorResult.Failed()) { return errorResult.ErrorCode(); };
+
+    nsAutoCString cachedValue;
+    cachedHeaders->Get(varyValues[i], cachedValue, errorResult);
+    if (errorResult.Failed()) { return errorResult.ErrorCode(); };
+
+    if (queryValue != cachedValue) {
+      varyHeadersMatch = false;
+      break;
+    }
+  }
+
+  *aSuccessOut = varyHeadersMatch;
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::DeleteEntries(mozIStorageConnection* aConn,
+                        const nsTArray<EntryId>& aEntryIdList,
+                        nsTArray<nsID>& aDeletedBodyIdListOut,
+                        uint32_t aPos, int32_t aLen)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  if (aEntryIdList.IsEmpty()) {
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(aPos < aEntryIdList.Length());
+
+  if (aLen < 0) {
+    aLen = aEntryIdList.Length() - aPos;
+  }
+
+  // Sqlite limits the number of entries allowed for an IN clause,
+  // so split up larger operations.
+  if (aLen > kMaxEntriesPerStatement) {
+    uint32_t curPos = aPos;
+    int32_t remaining = aLen;
+    while (remaining > 0) {
+      int32_t max = kMaxEntriesPerStatement;
+      int32_t curLen = std::min(max, remaining);
+      nsresult rv = DeleteEntries(aConn, aEntryIdList, aDeletedBodyIdListOut,
+                                  curPos, curLen);
+      if (NS_FAILED(rv)) { return rv; }
+
+      curPos += curLen;
+      remaining -= curLen;
+    }
+    return NS_OK;
+  }
+
+  nsCOMPtr<mozIStorageStatement> state;
+  nsAutoCString query(
+    "SELECT request_body_id, response_body_id FROM entries WHERE id IN ("
+  );
+  AppendListParamsToQuery(query, aEntryIdList, aPos, aLen);
+  query.AppendLiteral(")");
+
+  nsresult rv = aConn->CreateStatement(query, getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = BindListParamsToQuery(state, aEntryIdList, aPos, aLen);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool hasMoreData = false;
+  while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
+    // extract 0 to 2 nsID structs per row
+    for (uint32_t i = 0; i < 2; ++i) {
+      bool isNull = false;
+
+      rv = state->GetIsNull(i, &isNull);
+      if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+      if (!isNull) {
+        nsID id;
+        rv = ExtractId(state, i, &id);
+        if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+        aDeletedBodyIdListOut.AppendElement(id);
+      }
+    }
+  }
+
+  // Dependent records removed via ON DELETE CASCADE
+
+  query = NS_LITERAL_CSTRING(
+    "DELETE FROM entries WHERE id IN ("
+  );
+  AppendListParamsToQuery(query, aEntryIdList, aPos, aLen);
+  query.AppendLiteral(")");
+
+  rv = aConn->CreateStatement(query, getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = BindListParamsToQuery(state, aEntryIdList, aPos, aLen);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
+                      const PCacheRequest& aRequest,
+                      const nsID* aRequestBodyId,
+                      const PCacheResponse& aResponse,
+                      const nsID* aResponseBodyId)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  nsCOMPtr<mozIStorageStatement> state;
+  nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "INSERT INTO entries ("
+      "request_method, "
+      "request_url, "
+      "request_url_no_query, "
+      "request_referrer, "
+      "request_headers_guard, "
+      "request_mode, "
+      "request_credentials, "
+      "request_body_id, "
+      "response_type, "
+      "response_url, "
+      "response_status, "
+      "response_status_text, "
+      "response_headers_guard, "
+      "response_body_id, "
+      "cache_id "
+    ") VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15)"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindUTF8StringParameter(0, aRequest.method());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindStringParameter(1, aRequest.url());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindStringParameter(2, aRequest.urlWithoutQuery());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindStringParameter(3, aRequest.referrer());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(4,
+    static_cast<int32_t>(aRequest.headersGuard()));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(5, static_cast<int32_t>(aRequest.mode()));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(6,
+    static_cast<int32_t>(aRequest.credentials()));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = BindId(state, 7, aRequestBodyId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(8, static_cast<int32_t>(aResponse.type()));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindStringParameter(9, aResponse.url());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(10, aResponse.status());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindUTF8StringParameter(11, aResponse.statusText());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(12,
+    static_cast<int32_t>(aResponse.headersGuard()));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = BindId(state, 13, aResponseBodyId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(14, aCacheId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT last_insert_rowid()"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool hasMoreData = false;
+  rv = state->ExecuteStep(&hasMoreData);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  int32_t entryId;
+  rv = state->GetInt32(0, &entryId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "INSERT INTO request_headers ("
+      "name, "
+      "value, "
+      "entry_id "
+    ") VALUES (?1, ?2, ?3)"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  const nsTArray<PHeadersEntry>& requestHeaders = aRequest.headers();
+  for (uint32_t i = 0; i < requestHeaders.Length(); ++i) {
+    rv = state->BindUTF8StringParameter(0, requestHeaders[i].name());
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = state->BindUTF8StringParameter(1, requestHeaders[i].value());
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = state->BindInt32Parameter(2, entryId);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = state->Execute();
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  }
+
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "INSERT INTO response_headers ("
+      "name, "
+      "value, "
+      "entry_id "
+    ") VALUES (?1, ?2, ?3)"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  const nsTArray<PHeadersEntry>& responseHeaders = aResponse.headers();
+  for (uint32_t i = 0; i < responseHeaders.Length(); ++i) {
+    rv = state->BindUTF8StringParameter(0, responseHeaders[i].name());
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = state->BindUTF8StringParameter(1, responseHeaders[i].value());
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = state->BindInt32Parameter(2, entryId);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = state->Execute();
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId,
+                       SavedResponse* aSavedResponseOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+  MOZ_ASSERT(aSavedResponseOut);
+
+  nsCOMPtr<mozIStorageStatement> state;
+  nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT "
+      "response_type, "
+      "response_url, "
+      "response_status, "
+      "response_status_text, "
+      "response_headers_guard, "
+      "response_body_id "
+    "FROM entries "
+    "WHERE id=?1;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, aEntryId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool hasMoreData = false;
+  rv = state->ExecuteStep(&hasMoreData);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  int32_t type;
+  rv = state->GetInt32(0, &type);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  aSavedResponseOut->mValue.type() = static_cast<ResponseType>(type);
+
+  rv = state->GetString(1, aSavedResponseOut->mValue.url());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  int32_t status;
+  rv = state->GetInt32(2, &status);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  aSavedResponseOut->mValue.status() = status;
+
+  rv = state->GetUTF8String(3, aSavedResponseOut->mValue.statusText());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  int32_t guard;
+  rv = state->GetInt32(4, &guard);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  aSavedResponseOut->mValue.headersGuard() =
+    static_cast<HeadersGuardEnum>(guard);
+
+  bool nullBody = false;
+  rv = state->GetIsNull(5, &nullBody);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  aSavedResponseOut->mHasBodyId = !nullBody;
+
+  if (aSavedResponseOut->mHasBodyId) {
+    rv = ExtractId(state, 5, &aSavedResponseOut->mBodyId);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  }
+
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT "
+      "name, "
+      "value "
+    "FROM response_headers "
+    "WHERE entry_id=?1;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, aEntryId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
+    PHeadersEntry header;
+
+    rv = state->GetUTF8String(0, header.name());
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = state->GetUTF8String(1, header.value());
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    aSavedResponseOut->mValue.headers().AppendElement(header);
+  }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId,
+                      SavedRequest* aSavedRequestOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+  MOZ_ASSERT(aSavedRequestOut);
+
+  nsCOMPtr<mozIStorageStatement> state;
+  nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT "
+      "request_method, "
+      "request_url, "
+      "request_url_no_query, "
+      "request_referrer, "
+      "request_headers_guard, "
+      "request_mode, "
+      "request_credentials, "
+      "request_body_id "
+    "FROM entries "
+    "WHERE id=?1;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, aEntryId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool hasMoreData = false;
+  rv = state->ExecuteStep(&hasMoreData);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->GetUTF8String(0, aSavedRequestOut->mValue.method());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->GetString(1, aSavedRequestOut->mValue.url());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->GetString(2, aSavedRequestOut->mValue.urlWithoutQuery());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->GetString(3, aSavedRequestOut->mValue.referrer());
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  int32_t guard;
+  rv = state->GetInt32(4, &guard);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  aSavedRequestOut->mValue.headersGuard() =
+    static_cast<HeadersGuardEnum>(guard);
+
+  int32_t mode;
+  rv = state->GetInt32(5, &mode);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  aSavedRequestOut->mValue.mode() = static_cast<RequestMode>(mode);
+
+  int32_t credentials;
+  rv = state->GetInt32(6, &credentials);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  aSavedRequestOut->mValue.credentials() =
+    static_cast<RequestCredentials>(credentials);
+
+  bool nullBody = false;
+  rv = state->GetIsNull(7, &nullBody);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  aSavedRequestOut->mHasBodyId = !nullBody;
+
+  if (aSavedRequestOut->mHasBodyId) {
+    rv = ExtractId(state, 7, &aSavedRequestOut->mBodyId);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  }
+
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT "
+      "name, "
+      "value "
+    "FROM request_headers "
+    "WHERE entry_id=?1;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(0, aEntryId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
+    PHeadersEntry header;
+
+    rv = state->GetUTF8String(0, header.name());
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = state->GetUTF8String(1, header.value());
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    aSavedRequestOut->mValue.headers().AppendElement(header);
+  }
+
+  return rv;
+}
+
+// static
+void
+DBSchema::AppendListParamsToQuery(nsACString& aQuery,
+                                  const nsTArray<EntryId>& aEntryIdList,
+                                  uint32_t aPos, int32_t aLen)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT((aPos + aLen) <= aEntryIdList.Length());
+  for (int32_t i = aPos; i < aLen; ++i) {
+    if (i == 0) {
+      aQuery.AppendLiteral("?");
+    } else {
+      aQuery.AppendLiteral(",?");
+    }
+  }
+}
+
+// static
+nsresult
+DBSchema::BindListParamsToQuery(mozIStorageStatement* aState,
+                                const nsTArray<EntryId>& aEntryIdList,
+                                uint32_t aPos, int32_t aLen)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT((aPos + aLen) <= aEntryIdList.Length());
+  for (int32_t i = aPos; i < aLen; ++i) {
+    nsresult rv = aState->BindInt32Parameter(i, aEntryIdList[i]);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  return NS_OK;
+}
+
+// static
+nsresult
+DBSchema::BindId(mozIStorageStatement* aState, uint32_t aPos, const nsID* aId)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aState);
+  nsresult rv;
+
+  if (!aId) {
+    rv = aState->BindNullParameter(aPos);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    return rv;
+  }
+
+  char idBuf[NSID_LENGTH];
+  aId->ToProvidedString(idBuf);
+  rv = aState->BindUTF8StringParameter(aPos, nsAutoCString(idBuf));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+// static
+nsresult
+DBSchema::ExtractId(mozIStorageStatement* aState, uint32_t aPos, nsID* aIdOut)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aState);
+  MOZ_ASSERT(aIdOut);
+
+  nsAutoCString idString;
+  nsresult rv = aState->GetUTF8String(aPos, idString);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool success = aIdOut->Parse(idString.get());
+  if (NS_WARN_IF(!success)) { return NS_ERROR_UNEXPECTED; }
+
+  return rv;
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/DBSchema.h
@@ -0,0 +1,141 @@
+/* -*- 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_DBSchema_h
+#define mozilla_dom_cache_DBSchema_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/cache/Types.h"
+#include "nsError.h"
+#include "nsString.h"
+#include "nsTArrayForwardDeclare.h"
+
+class mozIStorageConnection;
+class mozIStorageStatement;
+struct nsID;
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class PCacheQueryParams;
+class PCacheRequest;
+class PCacheRequestOrVoid;
+class PCacheResponse;
+class PCacheResponseOrVoid;
+struct SavedRequest;
+struct SavedResponse;
+
+// TODO: remove static class and use functions in cache namespace (bug 1110485)
+class DBSchema MOZ_FINAL
+{
+public:
+  static nsresult CreateSchema(mozIStorageConnection* aConn);
+
+  static nsresult CreateCache(mozIStorageConnection* aConn,
+                              CacheId* aCacheIdOut);
+  // TODO: improve naming (confusing with CacheDelete) (bug 1110485)
+  static nsresult DeleteCache(mozIStorageConnection* aConn, CacheId aCacheId,
+                              nsTArray<nsID>& aDeletedBodyIdListOut);
+
+  // TODO: Consider removing unused IsCacheOrphaned after writing cleanup code. (bug 1110446)
+  static nsresult IsCacheOrphaned(mozIStorageConnection* aConn,
+                                  CacheId aCacheId, bool* aOrphanedOut);
+
+  static nsresult CacheMatch(mozIStorageConnection* aConn, CacheId aCacheId,
+                             const PCacheRequest& aRequest,
+                             const PCacheQueryParams& aParams,
+                             bool* aFoundResponseOut,
+                             SavedResponse* aSavedResponseOut);
+  static nsresult CacheMatchAll(mozIStorageConnection* aConn, CacheId aCacheId,
+                                const PCacheRequestOrVoid& aRequestOrVoid,
+                                const PCacheQueryParams& aParams,
+                                nsTArray<SavedResponse>& aSavedResponsesOut);
+  static nsresult CachePut(mozIStorageConnection* aConn, CacheId aCacheId,
+                           const PCacheRequest& aRequest,
+                           const nsID* aRequestBodyId,
+                           const PCacheResponse& aResponse,
+                           const nsID* aResponseBodyId,
+                           nsTArray<nsID>& aDeletedBodyIdListOut);
+  static nsresult CacheDelete(mozIStorageConnection* aConn, CacheId aCacheId,
+                              const PCacheRequest& aRequest,
+                              const PCacheQueryParams& aParams,
+                              nsTArray<nsID>& aDeletedBodyIdListOut,
+                              bool* aSuccessOut);
+  static nsresult CacheKeys(mozIStorageConnection* aConn, CacheId aCacheId,
+                            const PCacheRequestOrVoid& aRequestOrVoid,
+                            const PCacheQueryParams& aParams,
+                            nsTArray<SavedRequest>& aSavedRequestsOut);
+
+  static nsresult StorageMatch(mozIStorageConnection* aConn,
+                               Namespace aNamespace,
+                               const PCacheRequest& aRequest,
+                               const PCacheQueryParams& aParams,
+                               bool* aFoundResponseOut,
+                               SavedResponse* aSavedResponseOut);
+  static nsresult StorageGetCacheId(mozIStorageConnection* aConn,
+                                    Namespace aNamespace, const nsAString& aKey,
+                                    bool* aFoundCacheOut, CacheId* aCacheIdOut);
+  static nsresult StoragePutCache(mozIStorageConnection* aConn,
+                                  Namespace aNamespace, const nsAString& aKey,
+                                  CacheId aCacheId);
+  static nsresult StorageForgetCache(mozIStorageConnection* aConn,
+                                     Namespace aNamespace,
+                                     const nsAString& aKey);
+  static nsresult StorageGetKeys(mozIStorageConnection* aConn,
+                                 Namespace aNamespace,
+                                 nsTArray<nsString>& aKeysOut);
+
+private:
+  typedef int32_t EntryId;
+
+  static nsresult QueryAll(mozIStorageConnection* aConn, CacheId aCacheId,
+                           nsTArray<EntryId>& aEntryIdListOut);
+  static nsresult QueryCache(mozIStorageConnection* aConn, CacheId aCacheId,
+                             const PCacheRequest& aRequest,
+                             const PCacheQueryParams& aParams,
+                             nsTArray<EntryId>& aEntryIdListOut,
+                             uint32_t aMaxResults = UINT32_MAX);
+  static nsresult MatchByVaryHeader(mozIStorageConnection* aConn,
+                                    const PCacheRequest& aRequest,
+                                    EntryId entryId, bool* aSuccessOut);
+  static nsresult DeleteEntries(mozIStorageConnection* aConn,
+                                const nsTArray<EntryId>& aEntryIdList,
+                                nsTArray<nsID>& aDeletedBodyIdListOut,
+                                uint32_t aPos=0, int32_t aLen=-1);
+  static nsresult InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
+                              const PCacheRequest& aRequest,
+                              const nsID* aRequestBodyId,
+                              const PCacheResponse& aResponse,
+                              const nsID* aResponseBodyId);
+  static nsresult ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId,
+                               SavedResponse* aSavedResponseOut);
+  static nsresult ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId,
+                              SavedRequest* aSavedRequestOut);
+
+  static void AppendListParamsToQuery(nsACString& aQuery,
+                                      const nsTArray<EntryId>& aEntryIdList,
+                                      uint32_t aPos, int32_t aLen);
+  static nsresult BindListParamsToQuery(mozIStorageStatement* aState,
+                                        const nsTArray<EntryId>& aEntryIdList,
+                                        uint32_t aPos, int32_t aLen);
+  static nsresult BindId(mozIStorageStatement* aState, uint32_t aPos,
+                         const nsID* aId);
+  static nsresult ExtractId(mozIStorageStatement* aState, uint32_t aPos,
+                            nsID* aIdOut);
+
+  DBSchema() = delete;
+  ~DBSchema() = delete;
+
+  static const int32_t kLatestSchemaVersion;
+  static const int32_t kMaxEntriesPerStatement;
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_DBSchema_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/Feature.cpp
@@ -0,0 +1,108 @@
+/* -*- 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/Feature.h"
+
+#include "mozilla/dom/cache/ActorChild.h"
+#include "WorkerPrivate.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+using mozilla::dom::workers::Running;
+using mozilla::dom::workers::Status;
+using mozilla::dom::workers::WorkerPrivate;
+
+// static
+already_AddRefed<Feature>
+Feature::Create(WorkerPrivate* aWorkerPrivate)
+{
+  MOZ_ASSERT(aWorkerPrivate);
+
+  nsRefPtr<Feature> feature = new Feature(aWorkerPrivate);
+
+  if (!aWorkerPrivate->AddFeature(aWorkerPrivate->GetJSContext(), feature)) {
+    return nullptr;
+  }
+
+  return feature.forget();
+}
+
+void
+Feature::AddActor(ActorChild* aActor)
+{
+  NS_ASSERT_OWNINGTHREAD(Feature);
+  MOZ_ASSERT(aActor);
+  MOZ_ASSERT(!mActorList.Contains(aActor));
+
+  mActorList.AppendElement(aActor);
+
+  // Allow an actor to be added after we've entered the Notifying case.  We
+  // can't stop the actor creation from racing with out destruction of the
+  // other actors and we need to wait for this extra one to close as well.
+  // Signal it should destroy itself right away.
+  if (mNotified) {
+    aActor->StartDestroy();
+  }
+}
+
+void
+Feature::RemoveActor(ActorChild* aActor)
+{
+  NS_ASSERT_OWNINGTHREAD(Feature);
+  MOZ_ASSERT(aActor);
+
+  DebugOnly<bool> removed = mActorList.RemoveElement(aActor);
+
+  MOZ_ASSERT(removed);
+  MOZ_ASSERT(!mActorList.Contains(aActor));
+}
+
+bool
+Feature::Notified() const
+{
+  return mNotified;
+}
+
+bool
+Feature::Notify(JSContext* aCx, Status aStatus)
+{
+  NS_ASSERT_OWNINGTHREAD(Feature);
+
+  if (aStatus <= Running || mNotified) {
+    return true;
+  }
+
+  mNotified = true;
+
+  // Start the asynchronous destruction of our actors.  These will call back
+  // into RemoveActor() once the actor is destroyed.
+  for (uint32_t i = 0; i < mActorList.Length(); ++i) {
+    mActorList[i]->StartDestroy();
+  }
+
+  return true;
+}
+
+Feature::Feature(WorkerPrivate* aWorkerPrivate)
+  : mWorkerPrivate(aWorkerPrivate)
+  , mNotified(false)
+{
+  MOZ_ASSERT(mWorkerPrivate);
+}
+
+Feature::~Feature()
+{
+  NS_ASSERT_OWNINGTHREAD(Feature);
+  MOZ_ASSERT(mActorList.IsEmpty());
+
+  mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(), this);
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/Feature.h
@@ -0,0 +1,54 @@
+/* -*- 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_Feature_h
+#define mozilla_dom_cache_Feature_h
+
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+#include "WorkerFeature.h"
+
+namespace mozilla {
+
+namespace workers {
+class WorkerPrivate;
+}
+
+namespace dom {
+namespace cache {
+
+class ActorChild;
+
+class Feature MOZ_FINAL : public workers::WorkerFeature
+{
+public:
+  static already_AddRefed<Feature> Create(workers::WorkerPrivate* aWorkerPrivate);
+
+  void AddActor(ActorChild* aActor);
+  void RemoveActor(ActorChild* aActor);
+
+  bool Notified() const;
+
+  // WorkerFeature methods
+  virtual bool Notify(JSContext* aCx, workers::Status aStatus) MOZ_OVERRIDE;
+
+private:
+  explicit Feature(workers::WorkerPrivate *aWorkerPrivate);
+  ~Feature();
+
+  workers::WorkerPrivate* mWorkerPrivate;
+  nsTArray<ActorChild*> mActorList;
+  bool mNotified;
+
+public:
+  NS_INLINE_DECL_REFCOUNTING(mozilla::dom::cache::Feature)
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_Feature_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/FetchPut.cpp
@@ -0,0 +1,451 @@
+/* -*- 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/FetchPut.h"
+
+#include "mozilla/dom/Fetch.h"
+#include "mozilla/dom/FetchDriver.h"
+#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/Request.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/ResponseBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/cache/ManagerId.h"
+#include "mozilla/dom/cache/PCacheTypes.h"
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class FetchPut::Runnable MOZ_FINAL : public nsRunnable
+{
+public:
+  explicit Runnable(FetchPut* aFetchPut)
+    : mFetchPut(aFetchPut)
+  {
+    MOZ_ASSERT(mFetchPut);
+  }
+
+  NS_IMETHOD Run() MOZ_OVERRIDE
+  {
+    if (NS_IsMainThread())
+    {
+      mFetchPut->DoFetchOnMainThread();
+      return NS_OK;
+    }
+
+    MOZ_ASSERT(mFetchPut->mInitiatingThread == NS_GetCurrentThread());
+
+    mFetchPut->DoPutOnWorkerThread();
+
+    // The FetchPut object must ultimately be freed on the worker thread,
+    // so make sure we release our reference here.  The runnable may end
+    // up getting deleted on the main thread.
+    mFetchPut = nullptr;
+
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<FetchPut> mFetchPut;
+};
+
+class FetchPut::FetchObserver MOZ_FINAL : public FetchDriverObserver
+{
+public:
+  explicit FetchObserver(FetchPut* aFetchPut)
+    : mFetchPut(aFetchPut)
+  {
+  }
+
+  virtual void OnResponseAvailable(InternalResponse* aResponse) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(!mInternalResponse);
+    mInternalResponse = aResponse;
+  }
+
+  virtual void OnResponseEnd() MOZ_OVERRIDE
+  {
+    mFetchPut->FetchComplete(this, mInternalResponse);
+    mFetchPut = nullptr;
+  }
+
+protected:
+  virtual ~FetchObserver() { }
+
+private:
+  nsRefPtr<FetchPut> mFetchPut;
+  nsRefPtr<InternalResponse> mInternalResponse;
+};
+
+// static
+nsresult
+FetchPut::Create(Listener* aListener, Manager* aManager,
+                 RequestId aRequestId, CacheId aCacheId,
+                 const nsTArray<PCacheRequest>& aRequests,
+                 const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreams,
+                 FetchPut** aFetchPutOut)
+{
+  MOZ_ASSERT(aRequests.Length() == aRequestStreams.Length());
+
+  // The FetchDriver requires that all requests have a referrer already set.
+#ifdef DEBUG
+  for (uint32_t i = 0; i < aRequests.Length(); ++i) {
+    if (aRequests[i].referrer() == EmptyString()) {
+      return NS_ERROR_UNEXPECTED;
+    }
+  }
+#endif
+
+  nsRefPtr<FetchPut> ref = new FetchPut(aListener, aManager, aRequestId, aCacheId,
+                                        aRequests, aRequestStreams);
+
+  nsresult rv = ref->DispatchToMainThread();
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  ref.forget(aFetchPutOut);
+
+  return NS_OK;
+}
+
+void
+FetchPut::ClearListener()
+{
+  MOZ_ASSERT(mListener);
+  mListener = nullptr;
+}
+
+FetchPut::FetchPut(Listener* aListener, Manager* aManager,
+                   RequestId aRequestId, CacheId aCacheId,
+                   const nsTArray<PCacheRequest>& aRequests,
+                   const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreams)
+  : mListener(aListener)
+  , mManager(aManager)
+  , mRequestId(aRequestId)
+  , mCacheId(aCacheId)
+  , mInitiatingThread(NS_GetCurrentThread())
+  , mStateList(aRequests.Length())
+  , mPendingCount(0)
+  , mResult(NS_OK)
+{
+  MOZ_ASSERT(mListener);
+  MOZ_ASSERT(mManager);
+  MOZ_ASSERT(aRequests.Length() == aRequestStreams.Length());
+
+  for (uint32_t i = 0; i < aRequests.Length(); ++i) {
+    State* s = mStateList.AppendElement();
+    s->mPCacheRequest = aRequests[i];
+    s->mRequestStream = aRequestStreams[i];
+  }
+
+  mManager->AddRefCacheId(mCacheId);
+}
+
+FetchPut::~FetchPut()
+{
+  MOZ_ASSERT(mInitiatingThread == NS_GetCurrentThread());
+  MOZ_ASSERT(!mListener);
+  mManager->RemoveListener(this);
+  mManager->ReleaseCacheId(mCacheId);
+}
+
+nsresult
+FetchPut::DispatchToMainThread()
+{
+  MOZ_ASSERT(!mRunnable);
+
+  nsRefPtr<nsIRunnable> runnable = new Runnable(this);
+
+  nsresult rv = NS_DispatchToMainThread(runnable, nsIThread::DISPATCH_NORMAL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT(!mRunnable);
+  mRunnable = runnable.forget();
+
+  return NS_OK;
+}
+
+void
+FetchPut::DispatchToInitiatingThread()
+{
+  MOZ_ASSERT(mRunnable);
+
+  nsresult rv = mInitiatingThread->Dispatch(mRunnable,
+                                            nsIThread::DISPATCH_NORMAL);
+  if (NS_FAILED(rv)) {
+    MOZ_CRASH("Failed to dispatch to worker thread after fetch completion.");
+  }
+
+  mRunnable = nullptr;
+}
+
+void
+FetchPut::DoFetchOnMainThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsRefPtr<ManagerId> managerId = mManager->GetManagerId();
+  nsCOMPtr<nsIPrincipal> principal = managerId->Principal();
+  mPendingCount = mStateList.Length();
+
+  nsCOMPtr<nsILoadGroup> loadGroup;
+  nsresult rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), principal);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    MaybeSetError(rv);
+    MaybeCompleteOnMainThread();
+    return;
+  }
+
+  for (uint32_t i = 0; i < mStateList.Length(); ++i) {
+    nsRefPtr<InternalRequest> internalRequest =
+      ToInternalRequest(mStateList[i].mPCacheRequest);
+
+    // If there is a stream we must clone it so that its still available
+    // to store in the cache later;
+    if (mStateList[i].mRequestStream) {
+      internalRequest->SetBody(mStateList[i].mRequestStream);
+      nsRefPtr<InternalRequest> clone = internalRequest->Clone();
+
+      // The copy construction clone above can change the source stream,
+      // so get it back out to use when we put this in the cache.
+      internalRequest->GetBody(getter_AddRefs(mStateList[i].mRequestStream));
+
+      internalRequest = clone;
+    }
+
+    nsRefPtr<FetchDriver> fetchDriver = new FetchDriver(internalRequest,
+                                                        principal,
+                                                        loadGroup);
+
+    mStateList[i].mFetchObserver = new FetchObserver(this);
+    rv = fetchDriver->Fetch(mStateList[i].mFetchObserver);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      MaybeSetError(rv);
+      mStateList[i].mFetchObserver = nullptr;
+      mPendingCount -= 1;
+      continue;
+    }
+  }
+
+  // If they all failed, then we might need to complete main thread immediately
+  MaybeCompleteOnMainThread();
+}
+
+void
+FetchPut::FetchComplete(FetchObserver* aObserver,
+                        InternalResponse* aInternalResponse)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (aInternalResponse->IsError() && NS_SUCCEEDED(mResult)) {
+    MaybeSetError(NS_ERROR_FAILURE);
+  }
+
+  for (uint32_t i = 0; i < mStateList.Length(); ++i) {
+    if (mStateList[i].mFetchObserver == aObserver) {
+      ErrorResult rv;
+      ToPCacheResponseWithoutBody(mStateList[i].mPCacheResponse,
+                                  *aInternalResponse, rv);
+      if (rv.Failed()) {
+        MaybeSetError(rv.ErrorCode());
+        return;
+      }
+      aInternalResponse->GetBody(getter_AddRefs(mStateList[i].mResponseStream));
+      mStateList[i].mFetchObserver = nullptr;
+      MOZ_ASSERT(mPendingCount > 0);
+      mPendingCount -= 1;
+      MaybeCompleteOnMainThread();
+      return;
+    }
+  }
+
+  MOZ_ASSERT_UNREACHABLE("Should never get called by unknown fetch observer.");
+}
+
+void
+FetchPut::MaybeCompleteOnMainThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mPendingCount > 0) {
+    return;
+  }
+
+  DispatchToInitiatingThread();
+}
+
+void
+FetchPut::DoPutOnWorkerThread()
+{
+  MOZ_ASSERT(mInitiatingThread == NS_GetCurrentThread());
+
+  if (NS_FAILED(mResult)) {
+    MaybeNotifyListener();
+    return;
+  }
+
+  // These allocate ~4.5k combined on the stack
+  nsAutoTArray<CacheRequestResponse, 16> putList;
+  nsAutoTArray<nsCOMPtr<nsIInputStream>, 16> requestStreamList;
+  nsAutoTArray<nsCOMPtr<nsIInputStream>, 16> responseStreamList;
+
+  putList.SetCapacity(mStateList.Length());
+  requestStreamList.SetCapacity(mStateList.Length());
+  responseStreamList.SetCapacity(mStateList.Length());
+
+  for (uint32_t i = 0; i < mStateList.Length(); ++i) {
+    // The spec requires us to catch if content tries to insert a set of
+    // requests that would overwrite each other.
+    if (MatchInPutList(mStateList[i].mPCacheRequest, putList)) {
+      MaybeSetError(NS_ERROR_DOM_INVALID_STATE_ERR);
+      MaybeNotifyListener();
+      return;
+    }
+
+    CacheRequestResponse* entry = putList.AppendElement();
+    entry->request() = mStateList[i].mPCacheRequest;
+    entry->response() = mStateList[i].mPCacheResponse;
+    requestStreamList.AppendElement(mStateList[i].mRequestStream.forget());
+    responseStreamList.AppendElement(mStateList[i].mResponseStream.forget());
+  }
+  mStateList.Clear();
+
+  mManager->CachePutAll(this, mRequestId, mCacheId, putList, requestStreamList,
+                        responseStreamList);
+}
+
+// static
+bool
+FetchPut::MatchInPutList(const PCacheRequest& aRequest,
+                         const nsTArray<CacheRequestResponse>& aPutList)
+{
+  // This method implements the SW spec QueryCache algorithm against an
+  // in memory array of Request/Response objects.  This essentially the
+  // same algorithm that is implemented in DBSchema.cpp.  Unfortunately
+  // we cannot unify them because when operating against the real database
+  // we don't want to load all request/response objects into memory.
+
+  if (!aRequest.method().LowerCaseEqualsLiteral("get") &&
+      !aRequest.method().LowerCaseEqualsLiteral("head")) {
+    return false;
+  }
+
+  nsRefPtr<InternalHeaders> requestHeaders =
+    new InternalHeaders(aRequest.headers());
+
+  for (uint32_t i = 0; i < aPutList.Length(); ++i) {
+    const PCacheRequest& cachedRequest = aPutList[i].request();
+    const PCacheResponse& cachedResponse = aPutList[i].response();
+
+    // If the URLs don't match, then just skip to the next entry.
+    if (aRequest.url() != cachedRequest.url()) {
+      continue;
+    }
+
+    nsRefPtr<InternalHeaders> cachedRequestHeaders =
+      new InternalHeaders(cachedRequest.headers());
+
+    nsRefPtr<InternalHeaders> cachedResponseHeaders =
+      new InternalHeaders(cachedResponse.headers());
+
+    nsAutoTArray<nsCString, 16> varyHeaders;
+    ErrorResult rv;
+    cachedResponseHeaders->GetAll(NS_LITERAL_CSTRING("vary"), varyHeaders, rv);
+    MOZ_ALWAYS_TRUE(!rv.Failed());
+
+    // Assume the vary headers match until we find a conflict
+    bool varyHeadersMatch = true;
+
+    for (uint32_t j = 0; j < varyHeaders.Length(); ++j) {
+      if (varyHeaders[i].EqualsLiteral("*")) {
+        continue;
+      }
+
+      // The VARY header could in theory contain an illegal header name.  So
+      // we need to detect the error in the Get() calls below.  Treat these
+      // as not matching.
+      ErrorResult headerRv;
+
+      nsAutoCString value;
+      requestHeaders->Get(varyHeaders[j], value, rv);
+      if (NS_WARN_IF(rv.Failed())) {
+        varyHeadersMatch = false;
+        break;
+      }
+
+      nsAutoCString cachedValue;
+      cachedRequestHeaders->Get(varyHeaders[j], value, rv);
+      if (NS_WARN_IF(rv.Failed())) {
+        varyHeadersMatch = false;
+        break;
+      }
+
+      if (value != cachedValue) {
+        varyHeadersMatch = false;
+        break;
+      }
+    }
+
+    // URL was equal and all vary headers match!
+    if (varyHeadersMatch) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void
+FetchPut::OnCachePutAll(RequestId aRequestId, nsresult aRv)
+{
+  MOZ_ASSERT(mInitiatingThread == NS_GetCurrentThread());
+  MaybeSetError(aRv);
+  MaybeNotifyListener();
+}
+
+void
+FetchPut::MaybeSetError(nsresult aRv)
+{
+  if (NS_FAILED(mResult) || NS_SUCCEEDED(aRv)) {
+    return;
+  }
+  mResult = aRv;
+}
+
+void
+FetchPut::MaybeNotifyListener()
+{
+  MOZ_ASSERT(mInitiatingThread == NS_GetCurrentThread());
+  if (!mListener) {
+    return;
+  }
+  mListener->OnFetchPut(this, mRequestId, mResult);
+}
+
+nsIGlobalObject*
+FetchPut::GetGlobalObject() const
+{
+  MOZ_CRASH("No global object in parent-size FetchPut operation!");
+}
+
+#ifdef DEBUG
+void
+FetchPut::AssertOwningThread() const
+{
+  MOZ_ASSERT(mInitiatingThread == NS_GetCurrentThread());
+}
+#endif
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/FetchPut.h
@@ -0,0 +1,122 @@
+/* -*- 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_FetchPut_h
+#define mozilla_dom_cache_FetchPut_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/cache/Manager.h"
+#include "mozilla/dom/cache/PCacheTypes.h"
+#include "mozilla/dom/cache/Types.h"
+#include "mozilla/dom/cache/TypeUtils.h"
+#include "nsRefPtr.h"
+#include "nsTArray.h"
+#include <utility>
+
+class nsIInputStream;
+class nsIRunnable;
+class nsIThread;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class OwningRequestOrScalarValueString;
+class Promise;
+class Request;
+class RequestOrScalarValueString;
+class Response;
+template<typename T> class Sequence;
+
+namespace cache {
+
+class FetchPut MOZ_FINAL : public Manager::Listener
+                         , public TypeUtils
+{
+public:
+  typedef std::pair<nsRefPtr<Request>, nsRefPtr<Response>> PutPair;
+
+  class Listener
+  {
+  public:
+    virtual void
+    OnFetchPut(FetchPut* aFetchPut, RequestId aRequestId, nsresult aRv) = 0;
+  };
+
+  static nsresult
+  Create(Listener* aListener, Manager* aManager,
+         RequestId aRequestId, CacheId aCacheId,
+         const nsTArray<PCacheRequest>& aRequests,
+         const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreams,
+         FetchPut** aFetchPutOut);
+
+  void ClearListener();
+
+private:
+  class Runnable;
+  class FetchObserver;
+  struct State
+  {
+    PCacheRequest mPCacheRequest;
+    nsCOMPtr<nsIInputStream> mRequestStream;
+    nsRefPtr<FetchObserver> mFetchObserver;
+    PCacheResponse mPCacheResponse;
+    nsCOMPtr<nsIInputStream> mResponseStream;
+
+    nsRefPtr<Request> mRequest;
+    nsRefPtr<Response> mResponse;
+  };
+
+  FetchPut(Listener* aListener, Manager* aManager,
+           RequestId aRequestId, CacheId aCacheId,
+           const nsTArray<PCacheRequest>& aRequests,
+           const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreams);
+  ~FetchPut();
+
+  nsresult DispatchToMainThread();
+  void DispatchToInitiatingThread();
+
+  void DoFetchOnMainThread();
+  void FetchComplete(FetchObserver* aObserver,
+                     InternalResponse* aInternalResponse);
+  void MaybeCompleteOnMainThread();
+
+  void DoPutOnWorkerThread();
+  static bool MatchInPutList(const PCacheRequest& aRequest,
+                             const nsTArray<CacheRequestResponse>& aPutList);
+  virtual void OnCachePutAll(RequestId aRequestId, nsresult aRv) MOZ_OVERRIDE;
+
+  void MaybeSetError(nsresult aRv);
+  void MaybeNotifyListener();
+
+  // TypeUtils methods
+  virtual nsIGlobalObject* GetGlobalObject() const MOZ_OVERRIDE;
+#ifdef DEBUG
+  virtual void AssertOwningThread() const MOZ_OVERRIDE;
+#endif
+
+  Listener* mListener;
+  nsRefPtr<Manager> mManager;
+  const RequestId mRequestId;
+  const CacheId mCacheId;
+  nsCOMPtr<nsIThread> mInitiatingThread;
+  nsTArray<State> mStateList;
+  uint32_t mPendingCount;
+  nsresult mResult;
+  nsCOMPtr<nsIRunnable> mRunnable;
+
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::cache::FetchPut)
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_FetchPut_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/FileUtils.cpp
@@ -0,0 +1,286 @@
+/* -*- 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/FileUtils.h"
+
+#include "mozilla/dom/quota/FileStreams.h"
+#include "mozilla/SnappyCompressOutputStream.h"
+#include "mozilla/unused.h"
+#include "nsIFile.h"
+#include "nsIUUIDGenerator.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+using mozilla::dom::quota::FileInputStream;
+using mozilla::dom::quota::FileOutputStream;
+using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
+using mozilla::unused;
+
+// static
+nsresult
+FileUtils::BodyCreateDir(nsIFile* aBaseDir)
+{
+  MOZ_ASSERT(aBaseDir);
+
+  nsCOMPtr<nsIFile> aBodyDir;
+  nsresult rv = aBaseDir->Clone(getter_AddRefs(aBodyDir));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = aBodyDir->Append(NS_LITERAL_STRING("morgue"));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = aBodyDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
+  if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
+    return NS_OK;
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+// static
+nsresult
+FileUtils::BodyGetCacheDir(nsIFile* aBaseDir, const nsID& aId,
+                           nsIFile** aCacheDirOut)
+{
+  MOZ_ASSERT(aBaseDir);
+  MOZ_ASSERT(aCacheDirOut);
+
+  *aCacheDirOut = nullptr;
+
+  nsresult rv = aBaseDir->Clone(aCacheDirOut);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  MOZ_ASSERT(*aCacheDirOut);
+
+  rv = (*aCacheDirOut)->Append(NS_LITERAL_STRING("morgue"));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  // Some file systems have poor performance when there are too many files
+  // in a single directory.  Mitigate this issue by spreading the body
+  // files out into sub-directories.  We use the last byte of the ID for
+  // the name of the sub-directory.
+  nsAutoString subDirName;
+  subDirName.AppendInt(aId.m3[7]);
+  rv = (*aCacheDirOut)->Append(subDirName);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = (*aCacheDirOut)->Create(nsIFile::DIRECTORY_TYPE, 0755);
+  if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
+    return NS_OK;
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+// static
+nsresult
+FileUtils::BodyStartWriteStream(const QuotaInfo& aQuotaInfo,
+                                nsIFile* aBaseDir, nsIInputStream* aSource,
+                                void* aClosure,
+                                nsAsyncCopyCallbackFun aCallback, nsID* aIdOut,
+                                nsISupports** aCopyContextOut)
+{
+  MOZ_ASSERT(aBaseDir);
+  MOZ_ASSERT(aSource);
+  MOZ_ASSERT(aClosure);
+  MOZ_ASSERT(aCallback);
+  MOZ_ASSERT(aIdOut);
+  MOZ_ASSERT(aCopyContextOut);
+
+  nsresult rv;
+  nsCOMPtr<nsIUUIDGenerator> idGen =
+    do_GetService("@mozilla.org/uuid-generator;1", &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = idGen->GenerateUUIDInPlace(aIdOut);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  nsCOMPtr<nsIFile> finalFile;
+  rv = BodyIdToFile(aBaseDir, *aIdOut, BODY_FILE_FINAL,
+                    getter_AddRefs(finalFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool exists;
+  rv = finalFile->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  if (NS_WARN_IF(exists)) { return NS_ERROR_FILE_ALREADY_EXISTS; }
+
+  nsCOMPtr<nsIFile> tmpFile;
+  rv = BodyIdToFile(aBaseDir, *aIdOut, BODY_FILE_TMP, getter_AddRefs(tmpFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = tmpFile->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  if (NS_WARN_IF(exists)) { return NS_ERROR_FILE_ALREADY_EXISTS; }
+
+  nsCOMPtr<nsIOutputStream> fileStream =
+    FileOutputStream::Create(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
+                             aQuotaInfo.mOrigin, tmpFile);
+  if (NS_WARN_IF(!fileStream)) { return NS_ERROR_UNEXPECTED; }
+
+  nsRefPtr<SnappyCompressOutputStream> compressed =
+    new SnappyCompressOutputStream(fileStream);
+
+  nsCOMPtr<nsIEventTarget> target =
+    do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+
+  rv = NS_AsyncCopy(aSource, compressed, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS,
+                    compressed->BlockSize(), aCallback, aClosure,
+                    true, true, // close streams
+                    aCopyContextOut);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+// static
+void
+FileUtils::BodyCancelWrite(nsIFile* aBaseDir, nsISupports* aCopyContext)
+{
+  MOZ_ASSERT(aBaseDir);
+  MOZ_ASSERT(aCopyContext);
+
+  nsresult rv = NS_CancelAsyncCopy(aCopyContext, NS_ERROR_ABORT);
+  unused << NS_WARN_IF(NS_FAILED(rv));
+
+  // The partially written file must be cleaned up after the async copy
+  // makes its callback.
+}
+
+// static
+nsresult
+FileUtils::BodyFinalizeWrite(nsIFile* aBaseDir, const nsID& aId)
+{
+  MOZ_ASSERT(aBaseDir);
+
+  nsCOMPtr<nsIFile> tmpFile;
+  nsresult rv = BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP, getter_AddRefs(tmpFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  nsCOMPtr<nsIFile> finalFile;
+  rv = BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL, getter_AddRefs(finalFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  nsAutoString finalFileName;
+  rv = finalFile->GetLeafName(finalFileName);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = tmpFile->RenameTo(nullptr, finalFileName);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+// static
+nsresult
+FileUtils::BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir,
+                    const nsID& aId, nsIInputStream** aStreamOut)
+{
+  MOZ_ASSERT(aBaseDir);
+  MOZ_ASSERT(aStreamOut);
+
+  nsCOMPtr<nsIFile> finalFile;
+  nsresult rv = BodyIdToFile(aBaseDir, aId, BODY_FILE_FINAL,
+                             getter_AddRefs(finalFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool exists;
+  rv = finalFile->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  if (NS_WARN_IF(!exists)) { return NS_ERROR_FILE_NOT_FOUND; }
+
+  nsCOMPtr<nsIInputStream> fileStream =
+    FileInputStream::Create(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup,
+                            aQuotaInfo.mOrigin, finalFile);
+  if (NS_WARN_IF(!fileStream)) { return NS_ERROR_UNEXPECTED; }
+
+  fileStream.forget(aStreamOut);
+
+  return rv;
+}
+
+// static
+nsresult
+FileUtils::BodyDeleteFiles(nsIFile* aBaseDir, const nsTArray<nsID>& aIdList)
+{
+  nsresult rv = NS_OK;
+
+  for (uint32_t i = 0; i < aIdList.Length(); ++i) {
+    nsCOMPtr<nsIFile> tmpFile;
+    rv = BodyIdToFile(aBaseDir, aIdList[i], BODY_FILE_TMP,
+                      getter_AddRefs(tmpFile));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = tmpFile->Remove(false /* recursive */);
+    if (rv == NS_ERROR_FILE_NOT_FOUND ||
+        rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+      rv = NS_OK;
+    }
+
+    // Only treat file deletion as a hard failure in DEBUG builds.  Users
+    // can unfortunately hit this on windows if anti-virus is scanning files,
+    // etc.
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    nsCOMPtr<nsIFile> finalFile;
+    rv = BodyIdToFile(aBaseDir, aIdList[i], BODY_FILE_FINAL,
+                      getter_AddRefs(finalFile));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = finalFile->Remove(false /* recursive */);
+    if (rv == NS_ERROR_FILE_NOT_FOUND ||
+        rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+      rv = NS_OK;
+    }
+
+    // Again, only treat removal as hard failure in debug build.
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+  }
+
+  return NS_OK;
+}
+
+// static
+nsresult
+FileUtils::BodyIdToFile(nsIFile* aBaseDir, const nsID& aId,
+                        BodyFileType aType, nsIFile** aBodyFileOut)
+{
+  MOZ_ASSERT(aBaseDir);
+  MOZ_ASSERT(aBodyFileOut);
+
+  *aBodyFileOut = nullptr;
+
+  nsresult rv = BodyGetCacheDir(aBaseDir, aId, aBodyFileOut);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  MOZ_ASSERT(*aBodyFileOut);
+
+  char idString[NSID_LENGTH];
+  aId.ToProvidedString(idString);
+
+  NS_ConvertASCIItoUTF16 fileName(idString);
+
+  if (aType == BODY_FILE_FINAL) {
+    fileName.AppendLiteral(".final");
+  } else {
+    fileName.AppendLiteral(".tmp");
+  }
+
+  rv = (*aBodyFileOut)->Append(fileName);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  return rv;
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/FileUtils.h
@@ -0,0 +1,68 @@
+/* -*- 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_FileUtils_h
+#define mozilla_dom_cache_FileUtils_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/cache/Types.h"
+#include "nsStreamUtils.h"
+#include "nsTArrayForwardDeclare.h"
+
+struct nsID;
+class nsIFile;
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+// TODO: remove static class and use functions in cache namespace (bug 1110485)
+class FileUtils MOZ_FINAL
+{
+public:
+  enum BodyFileType
+  {
+    BODY_FILE_FINAL,
+    BODY_FILE_TMP
+  };
+
+  static nsresult BodyCreateDir(nsIFile* aBaseDir);
+  static nsresult BodyGetCacheDir(nsIFile* aBaseDir, const nsID& aId,
+                                  nsIFile** aCacheDirOut);
+
+  static nsresult
+  BodyStartWriteStream(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir,
+                       nsIInputStream* aSource, void* aClosure,
+                       nsAsyncCopyCallbackFun aCallback, nsID* aIdOut,
+                       nsISupports** aCopyContextOut);
+
+  static void
+  BodyCancelWrite(nsIFile* aBaseDir, nsISupports* aCopyContext);
+
+  static nsresult
+  BodyFinalizeWrite(nsIFile* aBaseDir, const nsID& aId);
+
+  static nsresult
+  BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsID& aId,
+           nsIInputStream** aStreamOut);
+
+  static nsresult
+  BodyDeleteFiles(nsIFile* aBaseDir, const nsTArray<nsID>& aIdList);
+
+private:
+  static nsresult
+  BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType,
+               nsIFile** aBodyFileOut);
+
+  FileUtils() = delete;
+  ~FileUtils() = delete;
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_FileUtils_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/IPCUtils.h
@@ -0,0 +1,22 @@
+/* -*- 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_IPCUtils_h
+#define mozilla_dom_cache_IPCUtils_h
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/dom/cache/Types.h"
+
+namespace IPC {
+  template<>
+  struct ParamTraits<mozilla::dom::cache::Namespace> :
+    public ContiguousEnumSerializer<mozilla::dom::cache::Namespace,
+                                    mozilla::dom::cache::DEFAULT_NAMESPACE,
+                                    mozilla::dom::cache::NUMBER_OF_NAMESPACES>
+  {};
+}
+
+#endif // mozilla_dom_cache_IPCUtils_h
new file mode 100644
--- /dev/null
+++ b/dom/cache/Manager.cpp
@@ -0,0 +1,1869 @@
+/* -*- 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/Manager.h"
+
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/unused.h"
+#include "mozilla/dom/cache/Context.h"
+#include "mozilla/dom/cache/DBAction.h"
+#include "mozilla/dom/cache/DBSchema.h"
+#include "mozilla/dom/cache/FileUtils.h"
+#include "mozilla/dom/cache/ManagerId.h"
+#include "mozilla/dom/cache/PCacheTypes.h"
+#include "mozilla/dom/cache/SavedTypes.h"
+#include "mozilla/dom/cache/StreamList.h"
+#include "mozilla/dom/cache/Types.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozStorageHelper.h"
+#include "nsAutoPtr.h"
+#include "nsIInputStream.h"
+#include "nsID.h"
+#include "nsIFile.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nsTObserverArray.h"
+
+namespace {
+
+using mozilla::unused;
+using mozilla::dom::cache::Action;
+using mozilla::dom::cache::DBSchema;
+using mozilla::dom::cache::FileUtils;
+using mozilla::dom::cache::QuotaInfo;
+using mozilla::dom::cache::SyncDBAction;
+
+// An Action that is executed when a Context is first created.  It ensures that
+// the directory and database are setup properly.  This lets other actions
+// not worry about these details.
+class SetupAction MOZ_FINAL : public SyncDBAction
+{
+public:
+  SetupAction()
+    : SyncDBAction(DBAction::Create)
+  { }
+
+  virtual nsresult
+  RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                        mozIStorageConnection* aConn) MOZ_OVERRIDE
+  {
+    // TODO: init maintainance marker (bug 1110446)
+    // TODO: perform maintainance if necessary (bug 1110446)
+    // TODO: find orphaned caches in database (bug 1110446)
+    // TODO: have Context create/delete marker files in constructor/destructor
+    //       and only do expensive maintenance if that marker is present (bug 1110446)
+
+    nsresult rv = FileUtils::BodyCreateDir(aDBDir);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    mozStorageTransaction trans(aConn, false,
+                                mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+    rv = DBSchema::CreateSchema(aConn);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = trans.Commit();
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    return rv;
+  }
+};
+
+// ----------------------------------------------------------------------------
+
+// Action that is executed when we determine that content has stopped using
+// a body file that has been orphaned.
+class DeleteOrphanedBodyAction MOZ_FINAL : public Action
+{
+public:
+  explicit DeleteOrphanedBodyAction(const nsTArray<nsID>& aDeletedBodyIdList)
+    : mDeletedBodyIdList(aDeletedBodyIdList)
+  { }
+
+  explicit DeleteOrphanedBodyAction(const nsID& aBodyId)
+  {
+    mDeletedBodyIdList.AppendElement(aBodyId);
+  }
+
+  virtual void
+  RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(aResolver);
+    MOZ_ASSERT(aQuotaInfo.mDir);
+
+    if (IsCanceled()) {
+      // TODO: handle orphaned files (bug 1110446)
+      aResolver->Resolve(NS_ERROR_ABORT);
+      return;
+    }
+
+    nsCOMPtr<nsIFile> dbDir;
+    nsresult rv = aQuotaInfo.mDir->Clone(getter_AddRefs(dbDir));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      aResolver->Resolve(rv);
+      return;
+    }
+
+    rv = dbDir->Append(NS_LITERAL_STRING("cache"));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      aResolver->Resolve(rv);
+      return;
+    }
+
+    rv = FileUtils::BodyDeleteFiles(dbDir, mDeletedBodyIdList);
+    unused << NS_WARN_IF(NS_FAILED(rv));
+
+    aResolver->Resolve(rv);
+  }
+
+private:
+  nsTArray<nsID> mDeletedBodyIdList;
+};
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+// ----------------------------------------------------------------------------
+
+// Singleton class to track Manager instances and ensure there is only
+// one for each unique ManagerId.
+class Manager::Factory
+{
+public:
+  friend class StaticAutoPtr<Manager::Factory>;
+
+  static nsresult
+  GetOrCreate(ManagerId* aManagerId, Manager** aManagerOut)
+  {
+    mozilla::ipc::AssertIsOnBackgroundThread();
+
+    // Ensure there is a factory instance.  This forces the Get() call
+    // below to use the same factory.
+    nsresult rv = MaybeCreateInstance();
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    nsRefPtr<Manager> ref = Get(aManagerId);
+    if (!ref) {
+      // TODO: replace this with a thread pool (bug 1119864)
+      nsCOMPtr<nsIThread> ioThread;
+      rv = NS_NewNamedThread("DOMCacheThread", getter_AddRefs(ioThread));
+      if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+      ref = new Manager(aManagerId, ioThread);
+
+      MOZ_ASSERT(!sFactory->mManagerList.Contains(ref));
+      sFactory->mManagerList.AppendElement(ref);
+    }
+
+    ref.forget(aManagerOut);
+
+    return NS_OK;
+  }
+
+  static already_AddRefed<Manager>
+  Get(ManagerId* aManagerId)
+  {
+    mozilla::ipc::AssertIsOnBackgroundThread();
+
+    nsresult rv = MaybeCreateInstance();
+    if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; }
+
+    ManagerList::ForwardIterator iter(sFactory->mManagerList);
+    while (iter.HasMore()) {
+      nsRefPtr<Manager> manager = iter.GetNext();
+      if (*manager->mManagerId == *aManagerId) {
+        return manager.forget();
+      }
+    }
+
+    return nullptr;
+  }
+
+  static void
+  Remove(Manager* aManager)
+  {
+    mozilla::ipc::AssertIsOnBackgroundThread();
+    MOZ_ASSERT(aManager);
+    MOZ_ASSERT(sFactory);
+
+    MOZ_ALWAYS_TRUE(sFactory->mManagerList.RemoveElement(aManager));
+
+    // clean up the factory singleton if there are no more managers
+    if (sFactory->mManagerList.IsEmpty()) {
+      DestroyInstance();
+    }
+  }
+
+  static void
+  StartShutdownAllOnMainThread()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // Lock for sFactoryShutdown and sBackgroundThread.
+    StaticMutexAutoLock lock(sMutex);
+
+    sFactoryShutdown = true;
+
+    if (!sBackgroundThread) {
+      return;
+    }
+
+    // Guaranteed to succeed because we should be shutdown before the
+    // background thread is destroyed.
+    nsCOMPtr<nsIRunnable> runnable = new ShutdownAllRunnable();
+    nsresult rv = sBackgroundThread->Dispatch(runnable,
+                                              nsIThread::DISPATCH_NORMAL);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv));
+  }
+
+  static bool
+  IsShutdownAllCompleteOnMainThread()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    StaticMutexAutoLock lock(sMutex);
+    // Infer whether we have shutdown using the sBackgroundThread value.  We
+    // guarantee this is nullptr when sFactory is destroyed.
+    return sFactoryShutdown && !sBackgroundThread;
+  }
+
+private:
+  Factory()
+  {
+    MOZ_COUNT_CTOR(cache::Manager::Factory);
+  }
+
+  ~Factory()
+  {
+    MOZ_COUNT_DTOR(cache::Manager::Factory);
+    MOZ_ASSERT(mManagerList.IsEmpty());
+  }
+
+  static nsresult
+  MaybeCreateInstance()
+  {
+    mozilla::ipc::AssertIsOnBackgroundThread();
+
+    if (!sFactory) {
+      // Be clear about what we are locking.  sFactory is bg thread only, so
+      // we don't need to lock it here.  Just protect sFactoryShutdown and
+      // sBackgroundThread.
+      {
+        StaticMutexAutoLock lock(sMutex);
+
+        if (sFactoryShutdown) {
+          return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+        }
+
+        // Cannot use ClearOnShutdown() because we're on the background thread.
+        // This is automatically cleared when Factory::Remove() calls
+        // DestroyInstance().
+        MOZ_ASSERT(!sBackgroundThread);
+        sBackgroundThread = NS_GetCurrentThread();
+      }
+
+      // We cannot use ClearOnShutdown() here because we're not on the main
+      // thread.  Instead, we delete sFactory in Factory::Remove() after the
+      // last manager is removed.  ShutdownObserver ensures this happens
+      // before shutdown.
+      sFactory = new Factory();
+    }
+
+    // Never return sFactory to code outside Factory.  We need to delete it
+    // out from under ourselves just before we return from Remove().  This
+    // would be (even more) dangerous if other code had a pointer to the
+    // factory itself.
+
+    return NS_OK;
+  }
+
+  static void
+  DestroyInstance()
+  {
+    mozilla::ipc::AssertIsOnBackgroundThread();
+    MOZ_ASSERT(sFactory);
+
+    // Be clear about what we are locking.  sFactory is bg thread only, so
+    // we don't need to lock it here.  Just protect sBackgroundThread.
+    {
+      StaticMutexAutoLock lock(sMutex);
+      MOZ_ASSERT(sBackgroundThread);
+      sBackgroundThread = nullptr;
+    }
+
+    sFactory = nullptr;
+  }
+
+  static void
+  ShutdownAllOnBackgroundThread()
+  {
+    mozilla::ipc::AssertIsOnBackgroundThread();
+
+    // The factory shutdown between when shutdown started on main thread and
+    // when we could start shutdown on the worker thread.  Just declare
+    // shutdown complete.  The sFactoryShutdown flag prevents the factory
+    // from racing to restart here.
+    if (!sFactory) {
+#ifdef DEBUG
+      StaticMutexAutoLock lock(sMutex);
+      MOZ_ASSERT(!sBackgroundThread);
+#endif
+      return;
+    }
+
+    MOZ_ASSERT(!sFactory->mManagerList.IsEmpty());
+
+    ManagerList::ForwardIterator iter(sFactory->mManagerList);
+    while (iter.HasMore()) {
+      nsRefPtr<Manager> manager = iter.GetNext();
+      manager->Shutdown();
+    }
+  }
+
+  class ShutdownAllRunnable MOZ_FINAL : public nsRunnable
+  {
+  public:
+    NS_IMETHOD
+    Run() MOZ_OVERRIDE
+    {
+      mozilla::ipc::AssertIsOnBackgroundThread();
+      ShutdownAllOnBackgroundThread();
+      return NS_OK;
+    }
+  private:
+    ~ShutdownAllRunnable() { }
+  };
+
+  // Singleton created on demand and deleted when last Manager is cleared
+  // in Remove().
+  // PBackground thread only.
+  static StaticAutoPtr<Factory> sFactory;
+
+  // protects following static attributes
+  static StaticMutex sMutex;
+
+  // Indicate if shutdown has occurred to block re-creation of sFactory.
+  // Must hold sMutex to access.
+  static bool sFactoryShutdown;
+
+  // Background thread owning all Manager objects.  Only set while sFactory is
+  // set.
+  // Must hold sMutex to access.
+  static StaticRefPtr<nsIThread> sBackgroundThread;
+
+  // Weak references as we don't want to keep Manager objects alive forever.
+  // When a Manager is destroyed it calls Factory::Remove() to clear itself.
+  // PBackground thread only.
+  typedef nsTObserverArray<Manager*> ManagerList;
+  ManagerList mManagerList;
+};
+
+// static
+StaticAutoPtr<Manager::Factory> Manager::Factory::sFactory;
+
+// static
+StaticMutex Manager::Factory::sMutex;
+
+// static
+bool Manager::Factory::sFactoryShutdown = false;
+
+// static
+StaticRefPtr<nsIThread> Manager::Factory::sBackgroundThread;
+
+// ----------------------------------------------------------------------------
+
+// Abstract class to help implement the various Actions.  The vast majority
+// of Actions are synchronous and need to report back to a Listener on the
+// Manager.
+class Manager::BaseAction : public SyncDBAction
+{
+protected:
+  BaseAction(Manager* aManager, ListenerId aListenerId, RequestId aRequestId)
+    : SyncDBAction(DBAction::Existing)
+    , mManager(aManager)
+    , mListenerId(aListenerId)
+    , mRequestId (aRequestId)
+  {
+  }
+
+  virtual void
+  Complete(Listener* aListener, nsresult aRv) = 0;
+
+  virtual void
+  CompleteOnInitiatingThread(nsresult aRv) MOZ_OVERRIDE
+  {
+    NS_ASSERT_OWNINGTHREAD(Manager::BaseAction);
+    Listener* listener = mManager->GetListener(mListenerId);
+    if (listener) {
+      Complete(listener, aRv);
+    }
+
+    // ensure we release the manager on the initiating thread
+    mManager = nullptr;
+  }
+
+  nsRefPtr<Manager> mManager;
+  const ListenerId mListenerId;
+  const RequestId mRequestId;
+};
+
+// ----------------------------------------------------------------------------
+
+// Action that is executed when we determine that content has stopped using
+// a Cache object that has been orphaned.
+class Manager::DeleteOrphanedCacheAction MOZ_FINAL : public SyncDBAction
+{
+public:
+  DeleteOrphanedCacheAction(Manager* aManager, CacheId aCacheId)
+    : SyncDBAction(DBAction::Existing)
+    , mManager(aManager)
+    , mCacheId(aCacheId)
+  { }
+
+  virtual nsresult
+  RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                        mozIStorageConnection* aConn) MOZ_OVERRIDE
+  {
+    mozStorageTransaction trans(aConn, false,
+                                mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+    nsresult rv = DBSchema::DeleteCache(aConn, mCacheId, mDeletedBodyIdList);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = trans.Commit();
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    return rv;
+  }
+
+  virtual void
+  CompleteOnInitiatingThread(nsresult aRv) MOZ_OVERRIDE
+  {
+    mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList);
+
+    // ensure we release the manager on the initiating thread
+    mManager = nullptr;
+  }
+
+private:
+  nsRefPtr<Manager> mManager;
+  const CacheId mCacheId;
+  nsTArray<nsID> mDeletedBodyIdList;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::CacheMatchAction MOZ_FINAL : public Manager::BaseAction
+{
+public:
+  CacheMatchAction(Manager* aManager, ListenerId aListenerId,
+                   RequestId aRequestId, CacheId aCacheId,
+                   const PCacheRequest& aRequest,
+                   const PCacheQueryParams& aParams,
+                   StreamList* aStreamList)
+    : BaseAction(aManager, aListenerId, aRequestId)
+    , mCacheId(aCacheId)
+    , mRequest(aRequest)
+    , mParams(aParams)
+    , mStreamList(aStreamList)
+    , mFoundResponse(false)
+  { }
+
+  virtual nsresult
+  RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                        mozIStorageConnection* aConn) MOZ_OVERRIDE
+  {
+    nsresult rv = DBSchema::CacheMatch(aConn, mCacheId, mRequest, mParams,
+                                       &mFoundResponse, &mResponse);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    if (!mFoundResponse || !mResponse.mHasBodyId) {
+      return rv;
+    }
+
+    nsCOMPtr<nsIInputStream> stream;
+    rv = FileUtils::BodyOpen(aQuotaInfo, aDBDir, mResponse.mBodyId,
+                             getter_AddRefs(stream));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    if (NS_WARN_IF(!stream)) { return NS_ERROR_FILE_NOT_FOUND; }
+
+    mStreamList->Add(mResponse.mBodyId, stream);
+
+    return rv;
+  }
+
+  virtual void
+  Complete(Listener* aListener, nsresult aRv) MOZ_OVERRIDE
+  {
+    if (!mFoundResponse) {
+      aListener->OnCacheMatch(mRequestId, aRv, nullptr, nullptr);
+    } else {
+      mStreamList->Activate(mCacheId);
+      aListener->OnCacheMatch(mRequestId, aRv, &mResponse, mStreamList);
+    }
+    mStreamList = nullptr;
+  }
+
+  virtual bool MatchesCacheId(CacheId aCacheId) const MOZ_OVERRIDE
+  {
+    return aCacheId == mCacheId;
+  }
+
+private:
+  const CacheId mCacheId;
+  const PCacheRequest mRequest;
+  const PCacheQueryParams mParams;
+  nsRefPtr<StreamList> mStreamList;
+  bool mFoundResponse;
+  SavedResponse mResponse;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::CacheMatchAllAction MOZ_FINAL : public Manager::BaseAction
+{
+public:
+  CacheMatchAllAction(Manager* aManager, ListenerId aListenerId,
+                      RequestId aRequestId, CacheId aCacheId,
+                      const PCacheRequestOrVoid& aRequestOrVoid,
+                      const PCacheQueryParams& aParams,
+                      StreamList* aStreamList)
+    : BaseAction(aManager, aListenerId, aRequestId)
+    , mCacheId(aCacheId)
+    , mRequestOrVoid(aRequestOrVoid)
+    , mParams(aParams)
+    , mStreamList(aStreamList)
+  { }
+
+  virtual nsresult
+  RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                        mozIStorageConnection* aConn) MOZ_OVERRIDE
+  {
+    nsresult rv = DBSchema::CacheMatchAll(aConn, mCacheId, mRequestOrVoid,
+                                          mParams, mSavedResponses);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    for (uint32_t i = 0; i < mSavedResponses.Length(); ++i) {
+      if (!mSavedResponses[i].mHasBodyId) {
+        continue;
+      }
+
+      nsCOMPtr<nsIInputStream> stream;
+      rv = FileUtils::BodyOpen(aQuotaInfo, aDBDir,
+                               mSavedResponses[i].mBodyId,
+                               getter_AddRefs(stream));
+      if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+      if (NS_WARN_IF(!stream)) { return NS_ERROR_FILE_NOT_FOUND; }
+
+      mStreamList->Add(mSavedResponses[i].mBodyId, stream);
+    }
+
+    return rv;
+  }
+
+  virtual void
+  Complete(Listener* aListener, nsresult aRv) MOZ_OVERRIDE
+  {
+    mStreamList->Activate(mCacheId);
+    aListener->OnCacheMatchAll(mRequestId, aRv, mSavedResponses, mStreamList);
+    mStreamList = nullptr;
+  }
+
+  virtual bool MatchesCacheId(CacheId aCacheId) const MOZ_OVERRIDE
+  {
+    return aCacheId == mCacheId;
+  }
+
+private:
+  const CacheId mCacheId;
+  const PCacheRequestOrVoid mRequestOrVoid;
+  const PCacheQueryParams mParams;
+  nsRefPtr<StreamList> mStreamList;
+  nsTArray<SavedResponse> mSavedResponses;
+};
+
+// ----------------------------------------------------------------------------
+
+// This is the most complex Action.  It puts a request/response pair into the
+// Cache.  It does not complete until all of the body data has been saved to
+// disk.  This means its an asynchronous Action.
+class Manager::CachePutAllAction MOZ_FINAL : public DBAction
+{
+public:
+  CachePutAllAction(Manager* aManager, ListenerId aListenerId,
+                    RequestId aRequestId, CacheId aCacheId,
+                    const nsTArray<CacheRequestResponse>& aPutList,
+                    const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
+                    const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList)
+    : DBAction(DBAction::Existing)
+    , mManager(aManager)
+    , mListenerId(aListenerId)
+    , mRequestId(aRequestId)
+    , mCacheId(aCacheId)
+    , mList(aPutList.Length())
+    , mExpectedAsyncCopyCompletions(1)
+    , mAsyncResult(NS_OK)
+    , mMutex("cache::Manager::CachePutAllAction")
+  {
+    MOZ_ASSERT(!aPutList.IsEmpty());
+    MOZ_ASSERT(aPutList.Length() == aRequestStreamList.Length());
+    MOZ_ASSERT(aPutList.Length() == aResponseStreamList.Length());
+
+    for (uint32_t i = 0; i < aPutList.Length(); ++i) {
+      Entry* entry = mList.AppendElement();
+      entry->mRequest = aPutList[i].request();
+      entry->mRequestStream = aRequestStreamList[i];
+      entry->mResponse = aPutList[i].response();
+      entry->mResponseStream = aResponseStreamList[i];
+    }
+  }
+
+private:
+  ~CachePutAllAction() { }
+
+  virtual void
+  RunWithDBOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo,
+                    nsIFile* aDBDir, mozIStorageConnection* aConn) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(aResolver);
+    MOZ_ASSERT(aDBDir);
+    MOZ_ASSERT(aConn);
+    MOZ_ASSERT(!mResolver);
+    MOZ_ASSERT(!mDBDir);
+    MOZ_ASSERT(!mConn);
+
+    MOZ_ASSERT(!mTargetThread);
+    mTargetThread = NS_GetCurrentThread();
+    MOZ_ASSERT(mTargetThread);
+
+    // We should be pre-initialized to expect one async completion.  This is
+    // the "manual" completion we call at the end of this method in all
+    // cases.
+    MOZ_ASSERT(mExpectedAsyncCopyCompletions == 1);
+
+    mResolver = aResolver;
+    mDBDir = aDBDir;
+    mConn = aConn;
+
+    // File bodies are streamed to disk via asynchronous copying.  Start
+    // this copying now.  Each copy will eventually result in a call
+    // to OnAsyncCopyComplete().
+    nsresult rv = NS_OK;
+    for (uint32_t i = 0; i < mList.Length(); ++i) {
+      rv = StartStreamCopy(aQuotaInfo, mList[i], RequestStream,
+                           &mExpectedAsyncCopyCompletions);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        break;
+      }
+
+      rv = StartStreamCopy(aQuotaInfo, mList[i], ResponseStream,
+                           &mExpectedAsyncCopyCompletions);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        break;
+      }
+    }
+
+
+    // Always call OnAsyncCopyComplete() manually here.  This covers the
+    // case where there is no async copying and also reports any startup
+    // errors correctly.  If we hit an error, then OnAsyncCopyComplete()
+    // will cancel any async copying.
+    OnAsyncCopyComplete(rv);
+  }
+
+  // Called once for each asynchronous file copy whether it succeeds or
+  // fails.  If a file copy is canceled, it still calls this method with
+  // an error code.
+  void
+  OnAsyncCopyComplete(nsresult aRv)
+  {
+    MOZ_ASSERT(mTargetThread == NS_GetCurrentThread());
+    MOZ_ASSERT(mConn);
+    MOZ_ASSERT(mResolver);
+    MOZ_ASSERT(mExpectedAsyncCopyCompletions > 0);
+
+    // Explicitly check for cancellation here to catch a race condition.
+    // Consider:
+    //
+    // 1) NS_AsyncCopy() executes on IO thread, but has not saved its
+    //    copy context yet.
+    // 2) CancelAllStreamCopying() occurs on PBackground thread
+    // 3) Copy context from (1) is saved on IO thread.
+    //
+    // Checking for cancellation here catches this condition when we
+    // first call OnAsyncCopyComplete() manually from RunWithDBOnTarget().
+    //
+    // This explicit cancellation check also handles the case where we
+    // are canceled just after all stream copying completes.  We should
+    // abort the synchronous DB operations in this case if we have not
+    // started them yet.
+    if (NS_SUCCEEDED(aRv) && IsCanceled()) {
+      aRv = NS_ERROR_ABORT;
+    }
+
+    // If any of the async copies fail, we need to still wait for them all to
+    // complete.  Cancel any other streams still working and remember the
+    // error.  All canceled streams will call OnAsyncCopyComplete().
+    if (NS_FAILED(aRv) && NS_SUCCEEDED(mAsyncResult)) {
+      CancelAllStreamCopying();
+      mAsyncResult = aRv;
+    }
+
+    // Check to see if async copying is still on-going.  If so, then simply
+    // return for now.  We must wait for a later OnAsyncCopyComplete() call.
+    mExpectedAsyncCopyCompletions -= 1;
+    if (mExpectedAsyncCopyCompletions > 0) {
+      return;
+    }
+
+    // We have finished with all async copying.  Indicate this by clearing all
+    // our copy contexts.
+    {
+      MutexAutoLock lock(mMutex);
+      mCopyContextList.Clear();
+    }
+
+    // An error occurred while async copying.  Terminate the Action.
+    // DoResolve() will clean up any files we may have written.
+    if (NS_FAILED(mAsyncResult)) {
+      DoResolve(mAsyncResult);
+      return;
+    }
+
+    mozStorageTransaction trans(mConn, false,
+                                mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+    nsresult rv = NS_OK;
+    for (uint32_t i = 0; i < mList.Length(); ++i) {
+      Entry& e = mList[i];
+      if (e.mRequestStream) {
+        rv = FileUtils::BodyFinalizeWrite(mDBDir, e.mRequestBodyId);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          DoResolve(rv);
+          return;
+        }
+      }
+      if (e.mResponseStream) {
+        rv = FileUtils::BodyFinalizeWrite(mDBDir, e.mResponseBodyId);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          DoResolve(rv);
+          return;
+        }
+      }
+
+      rv = DBSchema::CachePut(mConn, mCacheId, e.mRequest,
+                              e.mRequestStream ? &e.mRequestBodyId : nullptr,
+                              e.mResponse,
+                              e.mResponseStream ? &e.mResponseBodyId : nullptr,
+                              mDeletedBodyIdList);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        DoResolve(rv);
+        return;
+      }
+    }
+
+    rv = trans.Commit();
+    unused << NS_WARN_IF(NS_FAILED(rv));
+
+    DoResolve(rv);
+  }
+
+  virtual void
+  CompleteOnInitiatingThread(nsresult aRv) MOZ_OVERRIDE
+  {
+    NS_ASSERT_OWNINGTHREAD(Action);
+
+    for (uint32_t i = 0; i < mList.Length(); ++i) {
+      mList[i].mRequestStream = nullptr;
+      mList[i].mResponseStream = nullptr;
+    }
+
+    mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList);
+
+    Listener* listener = mManager->GetListener(mListenerId);
+    mManager = nullptr;
+    if (listener) {
+      listener->OnCachePutAll(mRequestId, aRv);
+    }
+  }
+
+  virtual void
+  CancelOnInitiatingThread() MOZ_OVERRIDE
+  {
+    NS_ASSERT_OWNINGTHREAD(Action);
+    Action::CancelOnInitiatingThread();
+    CancelAllStreamCopying();
+  }
+
+  virtual bool MatchesCacheId(CacheId aCacheId) const MOZ_OVERRIDE
+  {
+    NS_ASSERT_OWNINGTHREAD(Action);
+    return aCacheId == mCacheId;
+  }
+
+  struct Entry
+  {
+    PCacheRequest mRequest;
+    nsCOMPtr<nsIInputStream> mRequestStream;
+    nsID mRequestBodyId;
+    nsCOMPtr<nsISupports> mRequestCopyContext;
+
+    PCacheResponse mResponse;
+    nsCOMPtr<nsIInputStream> mResponseStream;
+    nsID mResponseBodyId;
+    nsCOMPtr<nsISupports> mResponseCopyContext;
+  };
+
+  enum StreamId
+  {
+    RequestStream,
+    ResponseStream
+  };
+
+  nsresult
+  StartStreamCopy(const QuotaInfo& aQuotaInfo, Entry& aEntry,
+                  StreamId aStreamId, uint32_t* aCopyCountOut)
+  {
+    MOZ_ASSERT(mTargetThread == NS_GetCurrentThread());
+    MOZ_ASSERT(aCopyCountOut);
+
+    if (IsCanceled()) {
+      return NS_ERROR_ABORT;
+    }
+
+    nsCOMPtr<nsIInputStream> source;
+    nsID* bodyId;
+
+    if (aStreamId == RequestStream) {
+      source = aEntry.mRequestStream;
+      bodyId = &aEntry.mRequestBodyId;
+    } else {
+      MOZ_ASSERT(aStreamId == ResponseStream);
+      source = aEntry.mResponseStream;
+      bodyId = &aEntry.mResponseBodyId;
+    }
+
+    if (!source) {
+      return NS_OK;
+    }
+
+    nsCOMPtr<nsISupports> copyContext;
+
+    nsresult rv = FileUtils::BodyStartWriteStream(aQuotaInfo, mDBDir, source,
+                                                  this, AsyncCopyCompleteFunc,
+                                                  bodyId,
+                                                  getter_AddRefs(copyContext));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    mBodyIdWrittenList.AppendElement(*bodyId);
+
+    if (copyContext) {
+      MutexAutoLock lock(mMutex);
+      mCopyContextList.AppendElement(copyContext);
+    }
+
+    *aCopyCountOut += 1;
+
+    return rv;
+  }
+
+  void
+  CancelAllStreamCopying()
+  {
+    // May occur on either owning thread or target thread
+    MutexAutoLock lock(mMutex);
+    for (uint32_t i = 0; i < mCopyContextList.Length(); ++i) {
+      FileUtils::BodyCancelWrite(mDBDir, mCopyContextList[i]);
+    }
+    mCopyContextList.Clear();
+  }
+
+  static void
+  AsyncCopyCompleteFunc(void* aClosure, nsresult aRv)
+  {
+    // May be on any thread, including STS event target.
+    MOZ_ASSERT(aClosure);
+    nsRefPtr<CachePutAllAction> action = static_cast<CachePutAllAction*>(aClosure);
+    action->CallOnAsyncCopyCompleteOnTargetThread(aRv);
+  }
+
+  void
+  CallOnAsyncCopyCompleteOnTargetThread(nsresult aRv)
+  {
+    // May be on any thread, including STS event target.
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableMethodWithArg<nsresult>(
+      this, &CachePutAllAction::OnAsyncCopyComplete, aRv);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      mTargetThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL)));
+  }
+
+  void
+  DoResolve(nsresult aRv)
+  {
+    MOZ_ASSERT(mTargetThread == NS_GetCurrentThread());
+
+    // DoResolve() must not be called until all async copying has completed.
+#ifdef DEBUG
+    {
+      MutexAutoLock lock(mMutex);
+      MOZ_ASSERT(mCopyContextList.IsEmpty());
+    }
+#endif
+
+    // Clean up any files we might have written before hitting the error.
+    if (NS_FAILED(aRv)) {
+      FileUtils::BodyDeleteFiles(mDBDir, mBodyIdWrittenList);
+    }
+
+    // Must be released on the target thread where it was opened.
+    mConn = nullptr;
+
+    // Drop our ref to the target thread as we are done with this thread.
+    // Also makes our thread assertions catch any incorrect method calls
+    // after resolve.
+    mTargetThread = nullptr;
+
+    // Make sure to de-ref the resolver per the Action API contract.
+    nsRefPtr<Action::Resolver> resolver;
+    mResolver.swap(resolver);
+    resolver->Resolve(aRv);
+  }
+
+  // initiating thread only
+  nsRefPtr<Manager> mManager;
+  const ListenerId mListenerId;
+  const RequestId mRequestId;
+
+  // Set on initiating thread, read on target thread.  State machine guarantees
+  // these are not modified while being read by the target thread.
+  const CacheId mCacheId;
+  nsTArray<Entry> mList;
+  uint32_t mExpectedAsyncCopyCompletions;
+
+  // target thread only
+  nsRefPtr<Resolver> mResolver;
+  nsCOMPtr<nsIFile> mDBDir;
+  nsCOMPtr<mozIStorageConnection> mConn;
+  nsCOMPtr<nsIThread> mTargetThread;
+  nsresult mAsyncResult;
+  nsTArray<nsID> mBodyIdWrittenList;
+
+  // Written to on target thread, accessed on initiating thread after target
+  // thread activity is guaranteed complete
+  nsTArray<nsID> mDeletedBodyIdList;
+
+  // accessed from any thread while mMutex locked
+  Mutex mMutex;
+  nsTArray<nsCOMPtr<nsISupports>> mCopyContextList;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::CacheDeleteAction MOZ_FINAL : public Manager::BaseAction
+{
+public:
+  CacheDeleteAction(Manager* aManager, ListenerId aListenerId,
+                    RequestId aRequestId, CacheId aCacheId,
+                    const PCacheRequest& aRequest,
+                    const PCacheQueryParams& aParams)
+    : BaseAction(aManager, aListenerId, aRequestId)
+    , mCacheId(aCacheId)
+    , mRequest(aRequest)
+    , mParams(aParams)
+    , mSuccess(false)
+  { }
+
+  virtual nsresult
+  RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                        mozIStorageConnection* aConn) MOZ_OVERRIDE
+  {
+    mozStorageTransaction trans(aConn, false,
+                                mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+    nsresult rv = DBSchema::CacheDelete(aConn, mCacheId, mRequest, mParams,
+                                        mDeletedBodyIdList, &mSuccess);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = trans.Commit();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mSuccess = false;
+      return rv;
+    }
+
+    return rv;
+  }
+
+  virtual void
+  Complete(Listener* aListener, nsresult aRv) MOZ_OVERRIDE
+  {
+    mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList);
+    aListener->OnCacheDelete(mRequestId, aRv, mSuccess);
+  }
+
+  virtual bool MatchesCacheId(CacheId aCacheId) const MOZ_OVERRIDE
+  {
+    return aCacheId == mCacheId;
+  }
+
+private:
+  const CacheId mCacheId;
+  const PCacheRequest mRequest;
+  const PCacheQueryParams mParams;
+  bool mSuccess;
+  nsTArray<nsID> mDeletedBodyIdList;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::CacheKeysAction MOZ_FINAL : public Manager::BaseAction
+{
+public:
+  CacheKeysAction(Manager* aManager, ListenerId aListenerId,
+                  RequestId aRequestId, CacheId aCacheId,
+                  const PCacheRequestOrVoid& aRequestOrVoid,
+                  const PCacheQueryParams& aParams,
+                  StreamList* aStreamList)
+    : BaseAction(aManager, aListenerId, aRequestId)
+    , mCacheId(aCacheId)
+    , mRequestOrVoid(aRequestOrVoid)
+    , mParams(aParams)
+    , mStreamList(aStreamList)
+  { }
+
+  virtual nsresult
+  RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                        mozIStorageConnection* aConn) MOZ_OVERRIDE
+  {
+    nsresult rv = DBSchema::CacheKeys(aConn, mCacheId, mRequestOrVoid, mParams,
+                                      mSavedRequests);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    for (uint32_t i = 0; i < mSavedRequests.Length(); ++i) {
+      if (!mSavedRequests[i].mHasBodyId) {
+        continue;
+      }
+
+      nsCOMPtr<nsIInputStream> stream;
+      rv = FileUtils::BodyOpen(aQuotaInfo, aDBDir,
+                               mSavedRequests[i].mBodyId,
+                               getter_AddRefs(stream));
+      if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+      if (NS_WARN_IF(!stream)) { return NS_ERROR_FILE_NOT_FOUND; }
+
+      mStreamList->Add(mSavedRequests[i].mBodyId, stream);
+    }
+
+    return rv;
+  }
+
+  virtual void
+  Complete(Listener* aListener, nsresult aRv) MOZ_OVERRIDE
+  {
+    mStreamList->Activate(mCacheId);
+    aListener->OnCacheKeys(mRequestId, aRv, mSavedRequests, mStreamList);
+    mStreamList = nullptr;
+  }
+
+  virtual bool MatchesCacheId(CacheId aCacheId) const MOZ_OVERRIDE
+  {
+    return aCacheId == mCacheId;
+  }
+
+private:
+  const CacheId mCacheId;
+  const PCacheRequestOrVoid mRequestOrVoid;
+  const PCacheQueryParams mParams;
+  nsRefPtr<StreamList> mStreamList;
+  nsTArray<SavedRequest> mSavedRequests;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::StorageMatchAction MOZ_FINAL : public Manager::BaseAction
+{
+public:
+  StorageMatchAction(Manager* aManager, ListenerId aListenerId,
+                     RequestId aRequestId, Namespace aNamespace,
+                     const PCacheRequest& aRequest,
+                     const PCacheQueryParams& aParams,
+                     StreamList* aStreamList)
+    : BaseAction(aManager, aListenerId, aRequestId)
+    , mNamespace(aNamespace)
+    , mRequest(aRequest)
+    , mParams(aParams)
+    , mStreamList(aStreamList)
+    , mFoundResponse(false)
+  { }
+
+  virtual nsresult
+  RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                        mozIStorageConnection* aConn) MOZ_OVERRIDE
+  {
+    nsresult rv = DBSchema::StorageMatch(aConn, mNamespace, mRequest, mParams,
+                                         &mFoundResponse, &mSavedResponse);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    if (!mFoundResponse || !mSavedResponse.mHasBodyId) {
+      return rv;
+    }
+
+    nsCOMPtr<nsIInputStream> stream;
+    rv = FileUtils::BodyOpen(aQuotaInfo, aDBDir, mSavedResponse.mBodyId,
+                             getter_AddRefs(stream));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    if (NS_WARN_IF(!stream)) { return NS_ERROR_FILE_NOT_FOUND; }
+
+    mStreamList->Add(mSavedResponse.mBodyId, stream);
+
+    return rv;
+  }
+
+  virtual void
+  Complete(Listener* aListener, nsresult aRv) MOZ_OVERRIDE
+  {
+    if (!mFoundResponse) {
+      aListener->OnStorageMatch(mRequestId, aRv, nullptr, nullptr);
+    } else {
+      mStreamList->Activate(mSavedResponse.mCacheId);
+      aListener->OnStorageMatch(mRequestId, aRv, &mSavedResponse, mStreamList);
+    }
+    mStreamList = nullptr;
+  }
+
+private:
+  const Namespace mNamespace;
+  const PCacheRequest mRequest;
+  const PCacheQueryParams mParams;
+  nsRefPtr<StreamList> mStreamList;
+  bool mFoundResponse;
+  SavedResponse mSavedResponse;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::StorageHasAction MOZ_FINAL : public Manager::BaseAction
+{
+public:
+  StorageHasAction(Manager* aManager, ListenerId aListenerId,
+                   RequestId aRequestId, Namespace aNamespace,
+                   const nsAString& aKey)
+    : BaseAction(aManager, aListenerId, aRequestId)
+    , mNamespace(aNamespace)
+    , mKey(aKey)
+    , mCacheFound(false)
+  { }
+
+  virtual nsresult
+  RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                        mozIStorageConnection* aConn) MOZ_OVERRIDE
+  {
+    CacheId cacheId;
+    return DBSchema::StorageGetCacheId(aConn, mNamespace, mKey,
+                                       &mCacheFound, &cacheId);
+  }
+
+  virtual void
+  Complete(Listener* aListener, nsresult aRv) MOZ_OVERRIDE
+  {
+    aListener->OnStorageHas(mRequestId, aRv, mCacheFound);
+  }
+
+private:
+  const Namespace mNamespace;
+  const nsString mKey;
+  bool mCacheFound;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::StorageOpenAction MOZ_FINAL : public Manager::BaseAction
+{
+public:
+  StorageOpenAction(Manager* aManager, ListenerId aListenerId,
+                    RequestId aRequestId, Namespace aNamespace,
+                    const nsAString& aKey)
+    : BaseAction(aManager, aListenerId, aRequestId)
+    , mNamespace(aNamespace)
+    , mKey(aKey)
+    , mCacheId(0)
+  { }
+
+  virtual nsresult
+  RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                        mozIStorageConnection* aConn) MOZ_OVERRIDE
+  {
+    // Cache does not exist, create it instead
+    mozStorageTransaction trans(aConn, false,
+                                mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+    // Look for existing cache
+    bool cacheFound;
+    nsresult rv = DBSchema::StorageGetCacheId(aConn, mNamespace, mKey,
+                                              &cacheFound, &mCacheId);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    if (cacheFound) {
+      return rv;
+    }
+
+    rv = DBSchema::CreateCache(aConn, &mCacheId);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = DBSchema::StoragePutCache(aConn, mNamespace, mKey, mCacheId);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = trans.Commit();
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    return rv;
+  }
+
+  virtual void
+  Complete(Listener* aListener, nsresult aRv) MOZ_OVERRIDE
+  {
+    aListener->OnStorageOpen(mRequestId, aRv, mCacheId);
+  }
+
+private:
+  const Namespace mNamespace;
+  const nsString mKey;
+  CacheId mCacheId;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::StorageDeleteAction MOZ_FINAL : public Manager::BaseAction
+{
+public:
+  StorageDeleteAction(Manager* aManager, ListenerId aListenerId,
+                      RequestId aRequestId, Namespace aNamespace,
+                      const nsAString& aKey)
+    : BaseAction(aManager, aListenerId, aRequestId)
+    , mNamespace(aNamespace)
+    , mKey(aKey)
+    , mCacheDeleted(false)
+    , mCacheId(0)
+  { }
+
+  virtual nsresult
+  RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                        mozIStorageConnection* aConn) MOZ_OVERRIDE
+  {
+    mozStorageTransaction trans(aConn, false,
+                                mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+    bool exists;
+    nsresult rv = DBSchema::StorageGetCacheId(aConn, mNamespace, mKey, &exists,
+                                              &mCacheId);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    if (!exists) {
+      mCacheDeleted = false;
+      return NS_OK;
+    }
+
+    rv = DBSchema::StorageForgetCache(aConn, mNamespace, mKey);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = trans.Commit();
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    mCacheDeleted = true;
+    return rv;
+  }
+
+  virtual void
+  Complete(Listener* aListener, nsresult aRv) MOZ_OVERRIDE
+  {
+    if (mCacheDeleted) {
+      // If content is referencing this cache, mark it orphaned to be
+      // deleted later.
+      if (!mManager->SetCacheIdOrphanedIfRefed(mCacheId)) {
+
+        // no outstanding references, delete immediately
+        nsRefPtr<Context> context = mManager->CurrentContext();
+        context->CancelForCacheId(mCacheId);
+        nsRefPtr<Action> action =
+          new DeleteOrphanedCacheAction(mManager, mCacheId);
+        context->Dispatch(mManager->mIOThread, action);
+      }
+    }
+
+    aListener->OnStorageDelete(mRequestId, aRv, mCacheDeleted);
+  }
+
+private:
+  const Namespace mNamespace;
+  const nsString mKey;
+  bool mCacheDeleted;
+  CacheId mCacheId;
+};
+
+// ----------------------------------------------------------------------------
+
+class Manager::StorageKeysAction MOZ_FINAL : public Manager::BaseAction
+{
+public:
+  StorageKeysAction(Manager* aManager, ListenerId aListenerId,
+                    RequestId aRequestId, Namespace aNamespace)
+    : BaseAction(aManager, aListenerId, aRequestId)
+    , mNamespace(aNamespace)
+  { }
+
+  virtual nsresult
+  RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
+                        mozIStorageConnection* aConn) MOZ_OVERRIDE
+  {
+    return DBSchema::StorageGetKeys(aConn, mNamespace, mKeys);
+  }
+
+  virtual void
+  Complete(Listener* aListener, nsresult aRv) MOZ_OVERRIDE
+  {
+    if (NS_FAILED(aRv)) {
+      mKeys.Clear();
+    }
+    aListener->OnStorageKeys(mRequestId, aRv, mKeys);
+  }
+
+private:
+  const Namespace mNamespace;
+  nsTArray<nsString> mKeys;
+};
+
+// ----------------------------------------------------------------------------
+
+//static
+Manager::ListenerId Manager::sNextListenerId = 0;
+
+// static
+nsresult
+Manager::GetOrCreate(ManagerId* aManagerId, Manager** aManagerOut)
+{
+  mozilla::ipc::AssertIsOnBackgroundThread();
+  return Factory::GetOrCreate(aManagerId, aManagerOut);
+}
+
+// static
+already_AddRefed<Manager>
+Manager::Get(ManagerId* aManagerId)
+{
+  mozilla::ipc::AssertIsOnBackgroundThread();
+  return Factory::Get(aManagerId);
+}
+
+// static
+void
+Manager::ShutdownAllOnMainThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  Factory::StartShutdownAllOnMainThread();
+
+  while (!Factory::IsShutdownAllCompleteOnMainThread()) {
+    if (!NS_ProcessNextEvent()) {
+      NS_WARNING("Something bad happened!");
+      break;
+    }
+  }
+}
+
+void
+Manager::RemoveListener(Listener* aListener)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  // There may not be a listener here in the case where an actor is killed
+  // before it can perform any actual async requests on Manager.
+  mListeners.RemoveElement(aListener, ListenerEntryListenerComparator());
+  MOZ_ASSERT(!mListeners.Contains(aListener,
+                                  ListenerEntryListenerComparator()));
+}
+
+void
+Manager::RemoveContext(Context* aContext)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(mContext);
+  MOZ_ASSERT(mContext == aContext);
+  mContext = nullptr;
+
+  // If we're trying to shutdown, then note that we're done.  This is the
+  // delayed case from Manager::Shutdown().
+  if (mShuttingDown) {
+    Factory::Remove(this);
+  }
+}
+
+void
+Manager::AddRefCacheId(CacheId aCacheId)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  for (uint32_t i = 0; i < mCacheIdRefs.Length(); ++i) {
+    if (mCacheIdRefs[i].mCacheId == aCacheId) {
+      mCacheIdRefs[i].mCount += 1;
+      return;
+    }
+  }
+  CacheIdRefCounter* entry = mCacheIdRefs.AppendElement();
+  entry->mCacheId = aCacheId;
+  entry->mCount = 1;
+  entry->mOrphaned = false;
+}
+
+void
+Manager::ReleaseCacheId(CacheId aCacheId)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  for (uint32_t i = 0; i < mCacheIdRefs.Length(); ++i) {
+    if (mCacheIdRefs[i].mCacheId == aCacheId) {
+      DebugOnly<uint32_t> oldRef = mCacheIdRefs[i].mCount;
+      mCacheIdRefs[i].mCount -= 1;
+      MOZ_ASSERT(mCacheIdRefs[i].mCount < oldRef);
+      if (mCacheIdRefs[i].mCount == 0) {
+        bool orphaned = mCacheIdRefs[i].mOrphaned;
+        mCacheIdRefs.RemoveElementAt(i);
+        // TODO: note that we need to check this cache for staleness on startup (bug 1110446)
+        if (orphaned && !mShuttingDown) {
+          nsRefPtr<Context> context = CurrentContext();
+          context->CancelForCacheId(aCacheId);
+          nsRefPtr<Action> action = new DeleteOrphanedCacheAction(this,
+                                                                  aCacheId);
+          context->Dispatch(mIOThread, action);
+        }
+      }
+      return;
+    }
+  }
+  MOZ_ASSERT_UNREACHABLE("Attempt to release CacheId that is not referenced!");
+}
+
+void
+Manager::AddRefBodyId(const nsID& aBodyId)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  for (uint32_t i = 0; i < mBodyIdRefs.Length(); ++i) {
+    if (mBodyIdRefs[i].mBodyId == aBodyId) {
+      mBodyIdRefs[i].mCount += 1;
+      return;
+    }
+  }
+  BodyIdRefCounter* entry = mBodyIdRefs.AppendElement();
+  entry->mBodyId = aBodyId;
+  entry->mCount = 1;
+  entry->mOrphaned = false;
+}
+
+void
+Manager::ReleaseBodyId(const nsID& aBodyId)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  for (uint32_t i = 0; i < mBodyIdRefs.Length(); ++i) {
+    if (mBodyIdRefs[i].mBodyId == aBodyId) {
+      DebugOnly<uint32_t> oldRef = mBodyIdRefs[i].mCount;
+      mBodyIdRefs[i].mCount -= 1;
+      MOZ_ASSERT(mBodyIdRefs[i].mCount < oldRef);
+      if (mBodyIdRefs[i].mCount < 1) {
+        bool orphaned = mBodyIdRefs[i].mOrphaned;
+        mBodyIdRefs.RemoveElementAt(i);
+        // TODO: note that we need to check this body for staleness on startup (bug 1110446)
+        if (orphaned && !mShuttingDown) {
+          nsRefPtr<Action> action = new DeleteOrphanedBodyAction(aBodyId);
+          nsRefPtr<Context> context = CurrentContext();
+          context->Dispatch(mIOThread, action);
+        }
+      }
+      return;
+    }
+  }
+  MOZ_ASSERT_UNREACHABLE("Attempt to release BodyId that is not referenced!");
+}
+
+already_AddRefed<ManagerId>
+Manager::GetManagerId() const
+{
+  nsRefPtr<ManagerId> ref = mManagerId;
+  return ref.forget();
+}
+
+void
+Manager::AddStreamList(StreamList* aStreamList)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(aStreamList);
+  mStreamLists.AppendElement(aStreamList);
+}
+
+void
+Manager::RemoveStreamList(StreamList* aStreamList)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(aStreamList);
+  mStreamLists.RemoveElement(aStreamList);
+}
+
+void
+Manager::CacheMatch(Listener* aListener, RequestId aRequestId, CacheId aCacheId,
+                    const PCacheRequest& aRequest,
+                    const PCacheQueryParams& aParams)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(aListener);
+  if (mShuttingDown) {
+    aListener->OnCacheMatch(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+                            nullptr, nullptr);
+    return;
+  }
+  nsRefPtr<Context> context = CurrentContext();
+  nsRefPtr<StreamList> streamList = new StreamList(this, context);
+  ListenerId listenerId = SaveListener(aListener);
+  nsRefPtr<Action> action = new CacheMatchAction(this, listenerId, aRequestId,
+                                                 aCacheId, aRequest, aParams,
+                                                 streamList);
+  context->Dispatch(mIOThread, action);
+}
+
+void
+Manager::CacheMatchAll(Listener* aListener, RequestId aRequestId,
+                       CacheId aCacheId, const PCacheRequestOrVoid& aRequest,
+                       const PCacheQueryParams& aParams)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(aListener);
+  if (mShuttingDown) {
+    aListener->OnCacheMatchAll(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+                               nsTArray<SavedResponse>(), nullptr);
+    return;
+  }
+  nsRefPtr<Context> context = CurrentContext();
+  nsRefPtr<StreamList> streamList = new StreamList(this, context);
+  ListenerId listenerId = SaveListener(aListener);
+  nsRefPtr<Action> action = new CacheMatchAllAction(this, listenerId, aRequestId,
+                                                    aCacheId, aRequest, aParams,
+                                                    streamList);
+  context->Dispatch(mIOThread, action);
+}
+
+void
+Manager::CachePutAll(Listener* aListener, RequestId aRequestId, CacheId aCacheId,
+                     const nsTArray<CacheRequestResponse>& aPutList,
+                     const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
+                     const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(aListener);
+  if (mShuttingDown) {
+    aListener->OnCachePutAll(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
+    return;
+  }
+  ListenerId listenerId = SaveListener(aListener);
+  nsRefPtr<Action> action = new CachePutAllAction(this, listenerId, aRequestId,
+                                                  aCacheId, aPutList,
+                                                  aRequestStreamList,
+                                                  aResponseStreamList);
+  nsRefPtr<Context> context = CurrentContext();
+  context->Dispatch(mIOThread, action);
+}
+
+void
+Manager::CacheDelete(Listener* aListener, RequestId aRequestId,
+                     CacheId aCacheId, const PCacheRequest& aRequest,
+                     const PCacheQueryParams& aParams)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(aListener);
+  if (mShuttingDown) {
+    aListener->OnCacheDelete(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, false);
+    return;
+  }
+  ListenerId listenerId = SaveListener(aListener);
+  nsRefPtr<Action> action = new CacheDeleteAction(this, listenerId, aRequestId,
+                                                  aCacheId, aRequest, aParams);
+  nsRefPtr<Context> context = CurrentContext();
+  context->Dispatch(mIOThread, action);
+}
+
+void
+Manager::CacheKeys(Listener* aListener, RequestId aRequestId,
+                   CacheId aCacheId, const PCacheRequestOrVoid& aRequestOrVoid,
+                   const PCacheQueryParams& aParams)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(aListener);
+  if (mShuttingDown) {
+    aListener->OnCacheKeys(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+                           nsTArray<SavedRequest>(), nullptr);
+    return;
+  }
+  nsRefPtr<Context> context = CurrentContext();
+  nsRefPtr<StreamList> streamList = new StreamList(this, context);
+  ListenerId listenerId = SaveListener(aListener);
+  nsRefPtr<Action> action = new CacheKeysAction(this, listenerId, aRequestId,
+                                                aCacheId, aRequestOrVoid,
+                                                aParams, streamList);
+  context->Dispatch(mIOThread, action);
+}
+
+void
+Manager::StorageMatch(Listener* aListener, RequestId aRequestId,
+                      Namespace aNamespace, const PCacheRequest& aRequest,
+                      const PCacheQueryParams& aParams)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(aListener);
+  if (mShuttingDown) {
+    aListener->OnStorageMatch(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+                              nullptr, nullptr);
+    return;
+  }
+  nsRefPtr<Context> context = CurrentContext();
+  nsRefPtr<StreamList> streamList = new StreamList(this, context);
+  ListenerId listenerId = SaveListener(aListener);
+  nsRefPtr<Action> action = new StorageMatchAction(this, listenerId, aRequestId,
+                                                   aNamespace, aRequest,
+                                                   aParams, streamList);
+  context->Dispatch(mIOThread, action);
+}
+
+void
+Manager::StorageHas(Listener* aListener, RequestId aRequestId,
+                    Namespace aNamespace, const nsAString& aKey)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(aListener);
+  if (mShuttingDown) {
+    aListener->OnStorageHas(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+                            false);
+    return;
+  }
+  ListenerId listenerId = SaveListener(aListener);
+  nsRefPtr<Action> action = new StorageHasAction(this, listenerId, aRequestId,
+                                                 aNamespace, aKey);
+  nsRefPtr<Context> context = CurrentContext();
+  context->Dispatch(mIOThread, action);
+}
+
+void
+Manager::StorageOpen(Listener* aListener, RequestId aRequestId,
+                     Namespace aNamespace, const nsAString& aKey)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(aListener);
+  if (mShuttingDown) {
+    aListener->OnStorageOpen(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, 0);
+    return;
+  }
+  ListenerId listenerId = SaveListener(aListener);
+  nsRefPtr<Action> action = new StorageOpenAction(this, listenerId, aRequestId,
+                                                  aNamespace, aKey);
+  nsRefPtr<Context> context = CurrentContext();
+  context->Dispatch(mIOThread, action);
+}
+
+void
+Manager::StorageDelete(Listener* aListener, RequestId aRequestId,
+                       Namespace aNamespace, const nsAString& aKey)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(aListener);
+  if (mShuttingDown) {
+    aListener->OnStorageDelete(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+                               false);
+    return;
+  }
+  ListenerId listenerId = SaveListener(aListener);
+  nsRefPtr<Action> action = new StorageDeleteAction(this, listenerId, aRequestId,
+                                                    aNamespace, aKey);
+  nsRefPtr<Context> context = CurrentContext();
+  context->Dispatch(mIOThread, action);
+}
+
+void
+Manager::StorageKeys(Listener* aListener, RequestId aRequestId,
+                     Namespace aNamespace)
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(aListener);
+  if (mShuttingDown) {
+    aListener->OnStorageKeys(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+                             nsTArray<nsString>());
+    return;
+  }
+  ListenerId listenerId = SaveListener(aListener);
+  nsRefPtr<Action> action = new StorageKeysAction(this, listenerId, aRequestId,
+                                                  aNamespace);
+  nsRefPtr<Context> context = CurrentContext();
+  context->Dispatch(mIOThread, action);
+}
+
+Manager::Manager(ManagerId* aManagerId, nsIThread* aIOThread)
+  : mM