Bug 1171170 - Consolidate/cache access to permissions, cycle collected objects in device storage. r=dhylands
authorAndrew Osmond <aosmond@mozilla.com>
Tue, 25 Aug 2015 20:13:34 -0400
changeset 260066 36d8ea398cc4ac00c99b0b4075de4f0f6bbb80f6
parent 260065 dbaf00db2ea83320d5c53e9a9f77b0e695f49d2c
child 260067 d413034042d3ba53885fdc7126a4d641badfe2e9
push id29299
push userryanvm@gmail.com
push dateMon, 31 Aug 2015 19:14:19 +0000
treeherdermozilla-central@a7183d2b82ed [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdhylands
bugs1171170
milestone43.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 1171170 - Consolidate/cache access to permissions, cycle collected objects in device storage. r=dhylands
dom/devicestorage/DeviceStorage.h
dom/devicestorage/DeviceStorageRequestChild.cpp
dom/devicestorage/DeviceStorageRequestChild.h
dom/devicestorage/nsDeviceStorage.cpp
dom/devicestorage/nsDeviceStorage.h
--- a/dom/devicestorage/DeviceStorage.h
+++ b/dom/devicestorage/DeviceStorage.h
@@ -36,19 +36,25 @@ class Blob;
 struct DeviceStorageEnumerationParameters;
 class DOMCursor;
 class DOMRequest;
 class Promise;
 class DeviceStorageFileSystem;
 } // namespace dom
 namespace ipc {
 class FileDescriptor;
+class PrincipalInfo;
 } // namespace ipc
 } // namespace mozilla
 
+class DeviceStorageRequest;
+class DeviceStorageCursorRequest;
+class DeviceStorageRequestManager;
+class nsDOMDeviceStorageCursor;
+
 class DeviceStorageFile final
   : public nsISupports {
 public:
   nsCOMPtr<nsIFile> mFile;
   nsString mStorageType;
   nsString mStorageName;
   nsString mRootDir;
   nsString mPath;
@@ -193,20 +199,16 @@ public:
   already_AddRefed<DOMRequest>
   AddNamed(mozilla::dom::Blob* aBlob, const nsAString& aPath, ErrorResult& aRv);
 
   already_AddRefed<DOMRequest>
   AppendNamed(mozilla::dom::Blob* aBlob, const nsAString& aPath,
               ErrorResult& aRv);
 
   already_AddRefed<DOMRequest>
-  AddOrAppendNamed(mozilla::dom::Blob* aBlob, const nsAString& aPath,
-                   const int32_t aRequestType, ErrorResult& aRv);
-
-  already_AddRefed<DOMRequest>
   Get(const nsAString& aPath, ErrorResult& aRv)
   {
     return GetInternal(aPath, false, aRv);
   }
   already_AddRefed<DOMRequest>
   GetEditable(const nsAString& aPath, ErrorResult& aRv)
   {
     return GetInternal(aPath, true, aRv);
@@ -285,27 +287,43 @@ public:
   // DeviceStorageStatics callbacks
   void OnFileWatcherUpdate(const nsCString& aData, DeviceStorageFile* aFile);
   void OnDiskSpaceWatcher(bool aLowDiskSpace);
   void OnWritableNameChanged();
 #ifdef MOZ_WIDGET_GONK
   void OnVolumeStateChanged(nsIVolume* aVolume);
 #endif
 
+  uint32_t CreateDOMRequest(DOMRequest** aRequest, ErrorResult& aRv);
+  uint32_t CreateDOMCursor(DeviceStorageCursorRequest* aRequest,
+                           nsDOMDeviceStorageCursor** aCursor,
+                           ErrorResult& aRv);
+  already_AddRefed<DOMRequest> CreateAndRejectDOMRequest(const char *aReason,
+                                                         ErrorResult& aRv);
+
+  nsresult CheckPermission(DeviceStorageRequest* aRequest);
+  void StorePermission(DeviceStorageRequest* aRequest, bool aAllow);
+
+  bool IsOwningThread();
+  nsresult DispatchToOwningThread(nsIRunnable* aRunnable);
+
 private:
   ~nsDOMDeviceStorage();
 
+  static nsresult CheckPrincipal(nsPIDOMWindow* aWindow, bool aIsAppsStorage,
+                                 nsIPrincipal** aPrincipal);
+
+  already_AddRefed<DOMRequest>
+  AddOrAppendNamed(mozilla::dom::Blob* aBlob, const nsAString& aPath,
+                   bool aCreate, ErrorResult& aRv);
+
   already_AddRefed<DOMRequest>
   GetInternal(const nsAString& aPath, bool aEditable, ErrorResult& aRv);
 
   void
-  GetInternal(nsPIDOMWindow* aWin, const nsAString& aPath, DOMRequest* aRequest,
-              bool aEditable);
-
-  void
   DeleteInternal(nsPIDOMWindow* aWin, const nsAString& aPath,
                  DOMRequest* aRequest);
 
   already_AddRefed<DOMCursor>
   EnumerateInternal(const nsAString& aName,
                     const EnumerationParameters& aOptions, bool aEditable,
                     ErrorResult& aRv);
 
@@ -322,34 +340,34 @@ private:
   already_AddRefed<nsDOMDeviceStorage>
     GetStorageByName(const nsAString &aStorageName);
 
   static already_AddRefed<nsDOMDeviceStorage>
     GetStorageByNameAndType(nsPIDOMWindow* aWin,
                             const nsAString& aStorageName,
                             const nsAString& aType);
 
-  nsCOMPtr<nsIPrincipal> mPrincipal;
-
-  bool mIsWatchingFile;
-  bool mAllowedToWatchFile;
   bool mIsDefaultLocation;
 
   nsresult Notify(const char* aReason, class DeviceStorageFile* aFile);
 
   friend class WatchFileEvent;
   friend class DeviceStorageRequest;
 
   static mozilla::StaticAutoPtr<nsTArray<nsString>> sVolumeNameCache;
 
 #ifdef MOZ_WIDGET_GONK
   nsString mLastStatus;
   nsString mLastStorageStatus;
   void DispatchStatusChangeEvent(nsAString& aStatus);
   void DispatchStorageStatusChangeEvent(nsAString& aStorageStatus);
 #endif
 
+  uint64_t mInnerWindowID;
   nsRefPtr<DeviceStorageFileSystem> mFileSystem;
+  nsRefPtr<DeviceStorageRequestManager> mManager;
+  nsAutoPtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
+  nsCOMPtr<nsIThread> mOwningThread;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsDOMDeviceStorage, NS_DOM_DEVICE_STORAGE_CID)
 
 #endif
--- a/dom/devicestorage/DeviceStorageRequestChild.cpp
+++ b/dom/devicestorage/DeviceStorageRequestChild.cpp
@@ -11,217 +11,157 @@
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/ipc/BlobChild.h"
 
 namespace mozilla {
 namespace dom {
 namespace devicestorage {
 
 DeviceStorageRequestChild::DeviceStorageRequestChild()
-  : mCallback(nullptr)
 {
   MOZ_COUNT_CTOR(DeviceStorageRequestChild);
 }
 
-DeviceStorageRequestChild::DeviceStorageRequestChild(DOMRequest* aRequest,
-                                                     DeviceStorageFile* aDSFile)
+DeviceStorageRequestChild::DeviceStorageRequestChild(DeviceStorageRequest* aRequest)
   : mRequest(aRequest)
-  , mDSFile(aDSFile)
-  , mCallback(nullptr)
 {
   MOZ_ASSERT(aRequest);
-  MOZ_ASSERT(aDSFile);
-  MOZ_COUNT_CTOR(DeviceStorageRequestChild);
-}
-
-DeviceStorageRequestChild::DeviceStorageRequestChild(DOMRequest* aRequest,
-                                                     DeviceStorageFile* aDSFile,
-                                                     DeviceStorageFileDescriptor* aDSFileDescriptor)
-  : mRequest(aRequest)
-  , mDSFile(aDSFile)
-  , mDSFileDescriptor(aDSFileDescriptor)
-  , mCallback(nullptr)
-{
-  MOZ_ASSERT(aRequest);
-  MOZ_ASSERT(aDSFile);
-  MOZ_ASSERT(aDSFileDescriptor);
   MOZ_COUNT_CTOR(DeviceStorageRequestChild);
 }
 
 DeviceStorageRequestChild::~DeviceStorageRequestChild() {
   MOZ_COUNT_DTOR(DeviceStorageRequestChild);
 }
 
 bool
 DeviceStorageRequestChild::
   Recv__delete__(const DeviceStorageResponseValue& aValue)
 {
-  if (mCallback) {
-    mCallback->RequestComplete();
-    mCallback = nullptr;
-  }
-
-  nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
-  if (!window) {
-    return true;
-  }
-
   switch (aValue.type()) {
 
     case DeviceStorageResponseValue::TErrorResponse:
     {
+      DS_LOG_INFO("error %u", mRequest->GetId());
       ErrorResponse r = aValue;
-      mRequest->FireError(r.error());
+      mRequest->Reject(r.error());
       break;
     }
 
     case DeviceStorageResponseValue::TSuccessResponse:
     {
+      DS_LOG_INFO("success %u", mRequest->GetId());
       nsString fullPath;
-      mDSFile->GetFullPath(fullPath);
-      AutoJSContext cx;
-      JS::Rooted<JS::Value> result(cx);
-      StringToJsval(window, fullPath, &result);
-      mRequest->FireSuccess(result);
+      mRequest->GetFile()->GetFullPath(fullPath);
+      mRequest->Resolve(fullPath);
       break;
     }
 
     case DeviceStorageResponseValue::TFileDescriptorResponse:
     {
+      DS_LOG_INFO("fd %u", mRequest->GetId());
       FileDescriptorResponse r = aValue;
 
+      DeviceStorageFile* file = mRequest->GetFile();
+      DeviceStorageFileDescriptor* descriptor = mRequest->GetFileDescriptor();
       nsString fullPath;
-      mDSFile->GetFullPath(fullPath);
-      AutoJSContext cx;
-      JS::Rooted<JS::Value> result(cx);
-      StringToJsval(window, fullPath, &result);
-
-      mDSFileDescriptor->mDSFile = mDSFile;
-      mDSFileDescriptor->mFileDescriptor = r.fileDescriptor();
-      mRequest->FireSuccess(result);
+      file->GetFullPath(fullPath);
+      descriptor->mDSFile = file;
+      descriptor->mFileDescriptor = r.fileDescriptor();
+      mRequest->Resolve(fullPath);
       break;
     }
 
     case DeviceStorageResponseValue::TBlobResponse:
     {
+      DS_LOG_INFO("blob %u", mRequest->GetId());
       BlobResponse r = aValue;
       BlobChild* actor = static_cast<BlobChild*>(r.blobChild());
-      nsRefPtr<BlobImpl> bloblImpl = actor->GetBlobImpl();
-      nsRefPtr<Blob> blob = Blob::Create(mRequest->GetParentObject(),
-                                         bloblImpl);
-
-      AutoJSContext cx;
-
-      JS::Rooted<JSObject*> obj(cx, blob->WrapObject(cx, nullptr));
-      MOZ_ASSERT(obj);
-
-      JS::Rooted<JS::Value> result(cx, JS::ObjectValue(*obj));
-      mRequest->FireSuccess(result);
+      nsRefPtr<BlobImpl> blobImpl = actor->GetBlobImpl();
+      mRequest->Resolve(blobImpl.get());
       break;
     }
 
     case DeviceStorageResponseValue::TFreeSpaceStorageResponse:
     {
+      DS_LOG_INFO("free %u", mRequest->GetId());
       FreeSpaceStorageResponse r = aValue;
-      AutoJSContext cx;
-      JS::Rooted<JS::Value> result(cx, JS_NumberValue(double(r.freeBytes())));
-      mRequest->FireSuccess(result);
+      mRequest->Resolve(r.freeBytes());
       break;
     }
 
     case DeviceStorageResponseValue::TUsedSpaceStorageResponse:
     {
+      DS_LOG_INFO("used %u", mRequest->GetId());
       UsedSpaceStorageResponse r = aValue;
-      AutoJSContext cx;
-      JS::Rooted<JS::Value> result(cx, JS_NumberValue(double(r.usedBytes())));
-      mRequest->FireSuccess(result);
+      mRequest->Resolve(r.usedBytes());
       break;
     }
 
     case DeviceStorageResponseValue::TAvailableStorageResponse:
     {
+      DS_LOG_INFO("available %u", mRequest->GetId());
       AvailableStorageResponse r = aValue;
-      AutoJSContext cx;
-      JS::Rooted<JS::Value> result(cx);
-      StringToJsval(window, r.mountState(), &result);
-      mRequest->FireSuccess(result);
+      mRequest->Resolve(r.mountState());
       break;
     }
 
     case DeviceStorageResponseValue::TStorageStatusResponse:
     {
+      DS_LOG_INFO("status %u", mRequest->GetId());
       StorageStatusResponse r = aValue;
-      AutoJSContext cx;
-      JS::Rooted<JS::Value> result(cx);
-      StringToJsval(window, r.storageStatus(), &result);
-      mRequest->FireSuccess(result);
+      mRequest->Resolve(r.storageStatus());
       break;
     }
 
     case DeviceStorageResponseValue::TFormatStorageResponse:
     {
+      DS_LOG_INFO("format %u", mRequest->GetId());
       FormatStorageResponse r = aValue;
-      AutoJSContext cx;
-      JS::Rooted<JS::Value> result(cx);
-      StringToJsval(window, r.mountState(), &result);
-      mRequest->FireSuccess(result);
+      mRequest->Resolve(r.mountState());
       break;
     }
 
     case DeviceStorageResponseValue::TMountStorageResponse:
     {
+      DS_LOG_INFO("mount %u", mRequest->GetId());
       MountStorageResponse r = aValue;
-      AutoJSContext cx;
-      JS::Rooted<JS::Value> result(cx);
-      StringToJsval(window, r.storageStatus(), &result);
-      mRequest->FireSuccess(result);
+      mRequest->Resolve(r.storageStatus());
       break;
     }
 
     case DeviceStorageResponseValue::TUnmountStorageResponse:
     {
+      DS_LOG_INFO("unmount %u", mRequest->GetId());
       UnmountStorageResponse r = aValue;
-      AutoJSContext cx;
-      JS::Rooted<JS::Value> result(cx);
-      StringToJsval(window, r.storageStatus(), &result);
-      mRequest->FireSuccess(result);
+      mRequest->Resolve(r.storageStatus());
       break;
     }
 
     case DeviceStorageResponseValue::TEnumerationResponse:
     {
+      DS_LOG_INFO("enumerate %u", mRequest->GetId());
       EnumerationResponse r = aValue;
-      nsDOMDeviceStorageCursor* cursor
-        = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
-
+      auto request = static_cast<DeviceStorageCursorRequest*>(mRequest.get());
       uint32_t count = r.paths().Length();
-      cursor->mFiles.SetCapacity(count);
+      request->AddFiles(count);
       for (uint32_t i = 0; i < count; i++) {
         nsRefPtr<DeviceStorageFile> dsf
           = new DeviceStorageFile(r.type(), r.paths()[i].storageName(),
                                   r.rootdir(), r.paths()[i].name());
-        cursor->mFiles.AppendElement(dsf.forget());
+        request->AddFile(dsf.forget());
       }
-
-      nsRefPtr<ContinueCursorEvent> event = new ContinueCursorEvent(cursor);
-      event->Continue();
+      request->Continue();
       break;
     }
 
     default:
     {
+      DS_LOG_ERROR("unknown %u", mRequest->GetId());
       NS_RUNTIMEABORT("not reached");
       break;
     }
   }
   return true;
 }
 
-void
-DeviceStorageRequestChild::
-  SetCallback(DeviceStorageRequestChildCallback *aCallback)
-{
-  mCallback = aCallback;
-}
-
 } // namespace devicestorage
 } // namespace dom
 } // namespace mozilla
--- a/dom/devicestorage/DeviceStorageRequestChild.h
+++ b/dom/devicestorage/DeviceStorageRequestChild.h
@@ -5,49 +5,40 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_devicestorage_DeviceStorageRequestChild_h
 #define mozilla_dom_devicestorage_DeviceStorageRequestChild_h
 
 #include "mozilla/dom/devicestorage/PDeviceStorageRequestChild.h"
 
 class DeviceStorageFile;
+class DeviceStorageRequest;
 struct DeviceStorageFileDescriptor;
 
 namespace mozilla {
 namespace dom {
 
-class DOMRequest;
-
 namespace devicestorage {
 
 class DeviceStorageRequestChildCallback
 {
   public:
     virtual void RequestComplete() = 0;
 };
 
 class DeviceStorageRequestChild : public PDeviceStorageRequestChild
 {
 public:
   DeviceStorageRequestChild();
-  DeviceStorageRequestChild(DOMRequest* aRequest, DeviceStorageFile* aFile);
-  DeviceStorageRequestChild(DOMRequest* aRequest, DeviceStorageFile* aFile,
-                            DeviceStorageFileDescriptor* aFileDescrptor);
+  explicit DeviceStorageRequestChild(DeviceStorageRequest* aRequest);
   ~DeviceStorageRequestChild();
 
-  void SetCallback(class DeviceStorageRequestChildCallback *aCallback);
-
   virtual bool Recv__delete__(const DeviceStorageResponseValue& value);
 
 private:
-  nsRefPtr<DOMRequest> mRequest;
-  nsRefPtr<DeviceStorageFile> mDSFile;
-  nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
-
-  DeviceStorageRequestChildCallback* mCallback;
+  nsRefPtr<DeviceStorageRequest> mRequest;
 };
 
 } // namespace devicestorage
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -21,16 +21,17 @@
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/LazyIdleThread.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/Services.h"
+#include "mozilla/ipc/BackgroundUtils.h" // for PrincipalInfoToPrincipal
 
 #include "nsArrayUtils.h"
 #include "nsAutoPtr.h"
 #include "nsGlobalWindow.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIFile.h"
 #include "nsIDirectoryEnumerator.h"
 #include "nsNetUtil.h"
@@ -236,17 +237,17 @@ DeviceStorageTypeChecker::InitFromBundle
     getter_Copies(mMusicExtensions));
   aBundle->GetStringFromName(
     NS_ConvertASCIItoUTF16(DEVICESTORAGE_VIDEOS).get(),
     getter_Copies(mVideosExtensions));
 }
 
 
 bool
-DeviceStorageTypeChecker::Check(const nsAString& aType, Blob* aBlob)
+DeviceStorageTypeChecker::Check(const nsAString& aType, BlobImpl* aBlob)
 {
   MOZ_ASSERT(aBlob);
 
   nsString mimeType;
   aBlob->GetType(mimeType);
 
   if (aType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
     return StringBeginsWith(mimeType, NS_LITERAL_STRING("image/"));
@@ -371,44 +372,59 @@ DeviceStorageTypeChecker::GetPermissionF
     return NS_ERROR_FAILURE;
   }
 
   aPermissionResult.AssignLiteral("device-storage:");
   aPermissionResult.Append(NS_ConvertUTF16toUTF8(aType));
   return NS_OK;
 }
 
-nsresult
-DeviceStorageTypeChecker::GetAccessForRequest(
-  const DeviceStorageRequestType aRequestType, nsACString& aAccessResult)
+size_t
+DeviceStorageTypeChecker::GetAccessIndexForRequest(
+  const DeviceStorageRequestType aRequestType)
 {
   switch(aRequestType) {
     case DEVICE_STORAGE_REQUEST_READ:
     case DEVICE_STORAGE_REQUEST_WATCH:
     case DEVICE_STORAGE_REQUEST_FREE_SPACE:
     case DEVICE_STORAGE_REQUEST_USED_SPACE:
     case DEVICE_STORAGE_REQUEST_AVAILABLE:
     case DEVICE_STORAGE_REQUEST_STATUS:
-      aAccessResult.AssignLiteral("read");
-      break;
+    case DEVICE_STORAGE_REQUEST_CURSOR:
+      return DEVICE_STORAGE_ACCESS_READ;
     case DEVICE_STORAGE_REQUEST_WRITE:
     case DEVICE_STORAGE_REQUEST_APPEND:
     case DEVICE_STORAGE_REQUEST_DELETE:
     case DEVICE_STORAGE_REQUEST_FORMAT:
     case DEVICE_STORAGE_REQUEST_MOUNT:
     case DEVICE_STORAGE_REQUEST_UNMOUNT:
-      aAccessResult.AssignLiteral("write");
-      break;
+      return DEVICE_STORAGE_ACCESS_WRITE;
     case DEVICE_STORAGE_REQUEST_CREATE:
     case DEVICE_STORAGE_REQUEST_CREATEFD:
-      aAccessResult.AssignLiteral("create");
-      break;
+      return DEVICE_STORAGE_ACCESS_CREATE;
     default:
-      aAccessResult.AssignLiteral("undefined");
-  }
+      return DEVICE_STORAGE_ACCESS_UNDEFINED;
+  }
+}
+
+nsresult
+DeviceStorageTypeChecker::GetAccessForRequest(
+  const DeviceStorageRequestType aRequestType, nsACString& aAccessResult)
+{
+  size_t access = GetAccessIndexForRequest(aRequestType);
+  return GetAccessForIndex(access, aAccessResult);
+}
+
+nsresult
+DeviceStorageTypeChecker::GetAccessForIndex(
+  size_t aAccessIndex, nsACString& aAccessResult)
+{
+  static const char *names[] = { "read", "write", "create", "undefined" };
+  MOZ_ASSERT(aAccessIndex < MOZ_ARRAY_LENGTH(names));
+  aAccessResult.AssignASCII(names[aAccessIndex]);
   return NS_OK;
 }
 
 static bool IsMediaType(const nsAString& aType)
 {
   return aType.EqualsLiteral(DEVICESTORAGE_PICTURES) ||
          aType.EqualsLiteral(DEVICESTORAGE_VIDEOS) ||
          aType.EqualsLiteral(DEVICESTORAGE_MUSIC) ||
@@ -554,16 +570,22 @@ DeviceStorageFile::Init()
 {
   DeviceStorageFile::GetRootDirectoryForType(mStorageType,
                                              mStorageName,
                                              getter_AddRefs(mFile));
 
   DebugOnly<DeviceStorageTypeChecker*> typeChecker
     = DeviceStorageTypeChecker::CreateOrGet();
   MOZ_ASSERT(typeChecker);
+
+  DS_LOG_INFO("type '%s' name '%s' root '%s' path '%s'",
+              NS_LossyConvertUTF16toASCII(mStorageType).get(),
+              NS_LossyConvertUTF16toASCII(mStorageName).get(),
+              NS_LossyConvertUTF16toASCII(mRootDir).get(),
+              NS_LossyConvertUTF16toASCII(mPath).get());
 }
 
 void
 DeviceStorageFile::GetFullPath(nsAString &aFullPath)
 {
   aFullPath.Truncate();
   if (!mStorageName.EqualsLiteral("")) {
     aFullPath.Append('/');
@@ -1407,1097 +1429,979 @@ nsDOMDeviceStorage::SetRootDirectoryForT
   DeviceStorageFile::GetRootDirectoryForType(aStorageType,
                                              aStorageName,
                                              getter_AddRefs(f));
   mRootDirectory = f;
   mStorageType = aStorageType;
   mStorageName = aStorageName;
 }
 
-JS::Value
-InterfaceToJsval(nsPIDOMWindow* aWindow,
-                 nsISupports* aObject,
-                 const nsIID* aIID)
-{
-  nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
-  if (!sgo) {
-    return JS::NullValue();
-  }
-
-  JSObject *unrootedScopeObj = sgo->GetGlobalJSObject();
-  NS_ENSURE_TRUE(unrootedScopeObj, JS::NullValue());
-  JSRuntime *runtime = JS_GetObjectRuntime(unrootedScopeObj);
-  JS::Rooted<JS::Value> someJsVal(runtime);
-  JS::Rooted<JSObject*> scopeObj(runtime, unrootedScopeObj);
-  nsresult rv;
-
-  { // Protect someJsVal from moving GC in ~JSAutoCompartment
-    AutoJSContext cx;
-    JSAutoCompartment ac(cx, scopeObj);
-
-    rv = nsContentUtils::WrapNative(cx, aObject, aIID, &someJsVal);
-  }
-  if (NS_FAILED(rv)) {
-    return JS::NullValue();
-  }
-
-  return someJsVal;
-}
-
-JS::Value
-nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aWindow);
-
-  if (!aFile) {
-    return JS::NullValue();
-  }
-
-  if (aFile->mEditable) {
-    // TODO - needs janv's file handle support.
-    return JS::NullValue();
-  }
-
-  nsString fullPath;
-  aFile->GetFullPath(fullPath);
-
-  // This check is useful to know if somewhere the DeviceStorageFile
-  // has not been properly set. Mimetype is not checked because it can be
-  // empty.
-  MOZ_ASSERT(aFile->mLength != UINT64_MAX);
-  MOZ_ASSERT(aFile->mLastModifiedDate != UINT64_MAX);
-
-  nsCOMPtr<nsIDOMBlob> blob = Blob::Create(aWindow,
-    new BlobImplFile(fullPath, aFile->mMimeType,
-                     aFile->mLength, aFile->mFile,
-                     aFile->mLastModifiedDate));
-  return InterfaceToJsval(aWindow, blob, &NS_GET_IID(nsIDOMBlob));
-}
-
-bool
-StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString,
-              JS::MutableHandle<JS::Value> result)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aWindow);
-
-  AutoJSAPI jsapi;
-  if (NS_WARN_IF(!jsapi.Init(aWindow))) {
-    return false;
-  }
-  JSContext* cx = jsapi.cx();
-
-  if (!xpc::StringToJsval(cx, aString, result)) {
-    return false;
-  }
-
-  return true;
-}
-
-class DeviceStorageCursorRequest final
-  : public nsIContentPermissionRequest
-{
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStorageCursorRequest,
-                                           nsIContentPermissionRequest)
-
-  NS_FORWARD_NSICONTENTPERMISSIONREQUEST(mCursor->);
-
-  explicit DeviceStorageCursorRequest(nsDOMDeviceStorageCursor* aCursor)
-    : mCursor(aCursor) { }
-
-private:
-  ~DeviceStorageCursorRequest() {}
-
-  nsRefPtr<nsDOMDeviceStorageCursor> mCursor;
-};
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeviceStorageCursorRequest)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
-  NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(DeviceStorageCursorRequest)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(DeviceStorageCursorRequest)
-
-NS_IMPL_CYCLE_COLLECTION(DeviceStorageCursorRequest,
-                         mCursor)
-
-
-class PostErrorEvent : public nsRunnable
-{
-public:
-  PostErrorEvent(already_AddRefed<DOMRequest> aRequest, const char* aMessage)
-    : mRequest(aRequest)
-  {
-    CopyASCIItoUTF16(aMessage, mError);
-  }
-
-  PostErrorEvent(DOMRequest* aRequest, const char* aMessage)
-    : mRequest(aRequest)
-  {
-    CopyASCIItoUTF16(aMessage, mError);
-  }
-
-  ~PostErrorEvent() {}
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    if (!mRequest->GetOwner()) {
-      return NS_OK;
-    }
-    mRequest->FireError(mError);
-    mRequest = nullptr;
-    return NS_OK;
-  }
-
-private:
-  nsRefPtr<DOMRequest> mRequest;
-  nsString mError;
-};
-
-ContinueCursorEvent::ContinueCursorEvent(already_AddRefed<DOMRequest> aRequest)
-  : mRequest(aRequest)
-{
-}
-
-ContinueCursorEvent::ContinueCursorEvent(DOMRequest* aRequest)
-  : mRequest(aRequest)
-{
-}
-
-already_AddRefed<DeviceStorageFile>
-ContinueCursorEvent::GetNextFile()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsDOMDeviceStorageCursor* cursor
-    = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
-  nsString cursorStorageType;
-  cursor->GetStorageType(cursorStorageType);
-
-  while (cursor->mIndex < cursor->mFiles.Length()) {
-    nsRefPtr<DeviceStorageFile> file = cursor->mFiles[cursor->mIndex].forget();
-    ++cursor->mIndex;
-    if (file) {
-      file->CalculateMimeType();
-      return file.forget();
-    }
-  }
-
-  return nullptr;
-}
-
-ContinueCursorEvent::~ContinueCursorEvent() {}
-
-void
-ContinueCursorEvent::Continue()
-{
-  if (XRE_IsParentProcess()) {
-    DebugOnly<nsresult> rv = NS_DispatchToMainThread(this);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    return;
-  }
-
-  nsRefPtr<DeviceStorageFile> file = GetNextFile();
-
-  if (!file) {
-    // done with enumeration.
-    DebugOnly<nsresult> rv = NS_DispatchToMainThread(this);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    return;
-  }
-
-  nsDOMDeviceStorageCursor* cursor
-    = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
-  nsString cursorStorageType;
-  cursor->GetStorageType(cursorStorageType);
-
-  DeviceStorageRequestChild* child
-    = new DeviceStorageRequestChild(mRequest, file);
-  child->SetCallback(cursor);
-  DeviceStorageGetParams params(cursorStorageType,
-                                file->mStorageName,
-                                file->mRootDir,
-                                file->mPath);
-  ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child,
-                                                                     params);
-  mRequest = nullptr;
-}
-
-NS_IMETHODIMP
-ContinueCursorEvent::Run()
-{
-  nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
-  if (!window) {
-    return NS_OK;
-  }
-
-  nsRefPtr<DeviceStorageFile> file = GetNextFile();
-
-  nsDOMDeviceStorageCursor* cursor
-    = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
-
-  AutoJSContext cx;
-  JS::Rooted<JS::Value> val(cx, nsIFileToJsval(window, file));
-
-  if (file) {
-    cursor->mOkToCallContinue = true;
-    cursor->FireSuccess(val);
-  } else {
-    cursor->FireDone();
-  }
-  mRequest = nullptr;
-  return NS_OK;
-}
-
-class InitCursorEvent : public nsRunnable
-{
-public:
-    InitCursorEvent(DOMRequest* aRequest, DeviceStorageFile* aFile)
-    : mFile(aFile)
-    , mRequest(aRequest)
-  {
-  }
-
-  ~InitCursorEvent() {}
-
-  NS_IMETHOD Run() {
-    MOZ_ASSERT(!NS_IsMainThread());
-
-    if (mFile->mFile) {
-      bool check;
-      mFile->mFile->IsDirectory(&check);
-      if (!check) {
-        nsCOMPtr<nsIRunnable> event =
-          new PostErrorEvent(mRequest.forget(),
-                             POST_ERROR_EVENT_FILE_NOT_ENUMERABLE);
-        return NS_DispatchToMainThread(event);
-      }
-    }
-
-    nsDOMDeviceStorageCursor* cursor
-      = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
-    mFile->CollectFiles(cursor->mFiles, cursor->mSince);
-
-    nsRefPtr<ContinueCursorEvent> event
-      = new ContinueCursorEvent(mRequest.forget());
-    event->Continue();
-
-    return NS_OK;
-  }
-
-
-private:
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsRefPtr<DOMRequest> mRequest;
-};
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMDeviceStorageCursor)
-  NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
-NS_INTERFACE_MAP_END_INHERITING(DOMCursor)
-
-NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStorageCursor, DOMCursor)
-NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorageCursor, DOMCursor)
-
-nsDOMDeviceStorageCursor::nsDOMDeviceStorageCursor(nsPIDOMWindow* aWindow,
-                                                   nsIPrincipal* aPrincipal,
-                                                   DeviceStorageFile* aFile,
-                                                   PRTime aSince)
-  : DOMCursor(aWindow, nullptr)
+nsDOMDeviceStorageCursor::nsDOMDeviceStorageCursor(nsIGlobalObject* aGlobal,
+                                                   DeviceStorageCursorRequest* aRequest)
+  : DOMCursor(aGlobal, nullptr)
   , mOkToCallContinue(false)
-  , mSince(aSince)
-  , mIndex(0)
-  , mFile(aFile)
-  , mPrincipal(aPrincipal)
-  , mRequester(new nsContentPermissionRequester(GetOwner()))
+  , mRequest(aRequest)
 {
 }
 
 nsDOMDeviceStorageCursor::~nsDOMDeviceStorageCursor()
 {
 }
 
 void
-nsDOMDeviceStorageCursor::GetStorageType(nsAString & aType)
-{
-  aType = mFile->mStorageType;
-}
-
-NS_IMETHODIMP
-nsDOMDeviceStorageCursor::GetTypes(nsIArray** aTypes)
+nsDOMDeviceStorageCursor::FireSuccess(JS::Handle<JS::Value> aResult)
 {
-  nsCString type;
-  nsresult rv =
-    DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, type);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsTArray<nsString> emptyOptions;
-  return nsContentPermissionUtils::CreatePermissionArray(type,
-                                                         NS_LITERAL_CSTRING("read"),
-                                                         emptyOptions,
-                                                         aTypes);
+  mOkToCallContinue = true;
+  DOMCursor::FireSuccess(aResult);
 }
 
-NS_IMETHODIMP
-nsDOMDeviceStorageCursor::GetPrincipal(nsIPrincipal** aRequestingPrincipal)
-{
-  NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsDOMDeviceStorageCursor::GetWindow(nsIDOMWindow** aRequestingWindow)
-{
-  NS_IF_ADDREF(*aRequestingWindow = GetOwner());
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsDOMDeviceStorageCursor::GetElement(nsIDOMElement** aRequestingElement)
-{
-  *aRequestingElement = nullptr;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsDOMDeviceStorageCursor::Cancel()
+void
+nsDOMDeviceStorageCursor::FireDone()
 {
-  nsCOMPtr<nsIRunnable> event
-    = new PostErrorEvent(this, POST_ERROR_EVENT_PERMISSION_DENIED);
-  return NS_DispatchToMainThread(event);
+  mRequest = nullptr;
+  DOMCursor::FireDone();
 }
 
-NS_IMETHODIMP
-nsDOMDeviceStorageCursor::Allow(JS::HandleValue aChoices)
+void
+nsDOMDeviceStorageCursor::FireError(const nsString& aReason)
 {
-  MOZ_ASSERT(aChoices.isUndefined());
-
-  if (!mFile->IsSafePath()) {
-    nsCOMPtr<nsIRunnable> r
-      = new PostErrorEvent(this, POST_ERROR_EVENT_PERMISSION_DENIED);
-    return NS_DispatchToMainThread(r);
-  }
-
-  if (!XRE_IsParentProcess()) {
-    PDeviceStorageRequestChild* child
-      = new DeviceStorageRequestChild(this, mFile);
-    DeviceStorageEnumerationParams params(mFile->mStorageType,
-                                          mFile->mStorageName,
-                                          mFile->mRootDir,
-                                          mSince);
-    ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child,
-                                                                       params);
-    return NS_OK;
-  }
-
-  nsCOMPtr<nsIEventTarget> target
-    = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
-  MOZ_ASSERT(target);
-
-  nsCOMPtr<nsIRunnable> event = new InitCursorEvent(this, mFile);
-  target->Dispatch(event, NS_DISPATCH_NORMAL);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsDOMDeviceStorageCursor::GetRequester(nsIContentPermissionRequester** aRequester)
-{
-  NS_ENSURE_ARG_POINTER(aRequester);
-
-  nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
-  requester.forget(aRequester);
-  return NS_OK;
+  mOkToCallContinue = false;
+  mRequest = nullptr;
+
+  if (!mResult.isUndefined()) {
+    // If we previously succeeded, we cannot fail without
+    // clearing the last result.
+    mResult.setUndefined();
+    mDone = false;
+  }
+
+  DOMCursor::FireError(aReason);
 }
 
 void
 nsDOMDeviceStorageCursor::Continue(ErrorResult& aRv)
 {
-  if (!mOkToCallContinue) {
+  if (!mOkToCallContinue || !mRequest) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
 
   if (!mResult.isUndefined()) {
     // We call onsuccess multiple times. Clear the last
     // result.
     mResult.setUndefined();
     mDone = false;
   }
 
-  nsRefPtr<ContinueCursorEvent> event = new ContinueCursorEvent(this);
-  event->Continue();
-
   mOkToCallContinue = false;
+  aRv = mRequest->Continue();
+}
+
+DeviceStorageRequest::DeviceStorageRequest()
+  : mId(DeviceStorageRequestManager::INVALID_ID)
+  , mAccess(DEVICE_STORAGE_ACCESS_UNDEFINED)
+  , mSendToParent(true)
+  , mUseStreamTransport(false)
+  , mCheckFile(false)
+  , mCheckBlob(false)
+  , mMultipleResolve(false)
+  , mPermissionCached(true)
+{
+  DS_LOG_DEBUG("%p", this);
+}
+
+DeviceStorageRequest::~DeviceStorageRequest()
+{
+  DS_LOG_DEBUG("%p", this);
+  if (mId != DeviceStorageRequestManager::INVALID_ID) {
+    /* Cursors may be freed without completing if the caller does not
+       call continue until there is no data left. */
+    MOZ_ASSERT(mMultipleResolve, "Still has valid ID but request being freed!");
+    Reject(POST_ERROR_EVENT_UNKNOWN);
+  }
+}
+
+void
+DeviceStorageRequest::Initialize(DeviceStorageRequestManager* aManager,
+                                 DeviceStorageFile* aFile,
+                                 uint32_t aId)
+{
+  DS_LOG_DEBUG("%p manages %p", aManager, this);
+  mManager = aManager;
+  mFile = aFile;
+  mId = aId;
+  MOZ_ASSERT(mManager);
+  MOZ_ASSERT(mFile);
+  MOZ_ASSERT(mId != DeviceStorageRequestManager::INVALID_ID);
+}
+
+void
+DeviceStorageRequest::Initialize(DeviceStorageRequestManager* aManager,
+                                 DeviceStorageFile* aFile,
+                                 uint32_t aRequest,
+                                 BlobImpl* aBlob)
+{
+  Initialize(aManager, aFile, aRequest);
+  mBlob = aBlob;
+  mCheckBlob = true;
+  mCheckFile = true;
+  MOZ_ASSERT(mBlob);
+}
+
+void
+DeviceStorageRequest::Initialize(DeviceStorageRequestManager* aManager,
+                                 DeviceStorageFile* aFile,
+                                 uint32_t aRequest,
+                                 DeviceStorageFileDescriptor* aDSFileDescriptor)
+{
+  Initialize(aManager, aFile, aRequest);
+  mDSFileDescriptor = aDSFileDescriptor;
+  MOZ_ASSERT(mDSFileDescriptor);
+}
+
+DeviceStorageAccessType
+DeviceStorageRequest::GetAccess() const
+{
+  return mAccess;
+}
+
+void
+DeviceStorageRequest::GetStorageType(nsAString& aType) const
+{
+  aType = mFile->mStorageType;
+}
+
+nsresult
+DeviceStorageRequest::Cancel()
+{
+  return Reject(POST_ERROR_EVENT_PERMISSION_DENIED);
+}
+
+nsresult
+DeviceStorageRequest::Allow()
+{
+  nsresult rv = AllowInternal();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return Reject(rv == NS_ERROR_ILLEGAL_VALUE
+                  ? POST_ERROR_EVENT_ILLEGAL_TYPE
+                  : POST_ERROR_EVENT_UNKNOWN);
+  }
+  return rv;
+}
+
+DeviceStorageFile*
+DeviceStorageRequest::GetFile() const
+{
+  MOZ_ASSERT(mFile);
+  return mFile;
+}
+
+DeviceStorageFileDescriptor*
+DeviceStorageRequest::GetFileDescriptor() const
+{
+  MOZ_ASSERT(mDSFileDescriptor);
+  return mDSFileDescriptor;
+}
+
+DeviceStorageRequestManager*
+DeviceStorageRequest::GetManager() const
+{
+  return mManager;
+}
+
+void
+DeviceStorageRequest::Prepare()
+{
+}
+
+nsresult
+DeviceStorageRequest::CreateSendParams(DeviceStorageParams& aParams)
+{
+  MOZ_ASSERT_UNREACHABLE("Cannot send to parent, missing param creator");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+DeviceStorageRequest::AllowInternal()
+{
+  MOZ_ASSERT(mManager->IsOwningThread() || NS_IsMainThread());
+  Prepare();
+
+  DeviceStorageTypeChecker* typeChecker
+    = DeviceStorageTypeChecker::CreateOrGet();
+  if (!typeChecker) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  if (mCheckBlob && (!mBlob ||
+      !typeChecker->Check(mFile->mStorageType, mBlob))) {
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+  if (mCheckFile && (!mFile->mFile ||
+      !typeChecker->Check(mFile->mStorageType, mFile->mFile))) {
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  mSendToParent = mSendToParent && !XRE_IsParentProcess();
+  if (mSendToParent) {
+    return SendToParentProcess();
+  }
+
+  if (mUseStreamTransport) {
+    DS_LOG_INFO("run stream transport %u", mId);
+    nsCOMPtr<nsIEventTarget> target
+      = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+    MOZ_ASSERT(target);
+    return target->Dispatch(this, NS_DISPATCH_NORMAL);
+  }
+
+  DS_LOG_INFO("run %u", mId);
+  return Run();
+}
+
+nsresult
+DeviceStorageRequest::SendToParentProcess()
+{
+  // PContent can only be used on the main thread
+  if (!NS_IsMainThread()) {
+    nsRefPtr<DeviceStorageRequest> self = this;
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
+    {
+      nsresult rv = self->SendToParentProcess();
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        self->Reject(POST_ERROR_EVENT_UNKNOWN);
+      }
+    });
+    return NS_DispatchToMainThread(r);
+  }
+
+  MOZ_ASSERT(NS_IsMainThread());
+  DS_LOG_INFO("request parent %u", mId);
+
+  DeviceStorageParams params;
+  nsresult rv = CreateSendParams(params);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(this);
+  ContentChild::GetSingleton()
+    ->SendPDeviceStorageRequestConstructor(child, params);
+  return NS_OK;
+}
+
+DeviceStorageCursorRequest::DeviceStorageCursorRequest()
+  : mIndex(0)
+  , mSince(0)
+{
+  mAccess = DEVICE_STORAGE_ACCESS_READ;
+  mUseStreamTransport = true;
+  mMultipleResolve = true;
+  DS_LOG_INFO("");
+}
+
+void
+DeviceStorageCursorRequest::Initialize(DeviceStorageRequestManager* aManager,
+                                       DeviceStorageFile* aFile,
+                                       uint32_t aRequest,
+                                       PRTime aSince)
+{
+  Initialize(aManager, aFile, aRequest);
+  mStorageType = mFile->mStorageType;
+  mSince = aSince;
+}
+
+void
+DeviceStorageCursorRequest::AddFiles(size_t aSize)
+{
+  mFiles.SetCapacity(mFiles.Length() + aSize);
 }
 
 void
-nsDOMDeviceStorageCursor::RequestComplete()
+DeviceStorageCursorRequest::AddFile(already_AddRefed<DeviceStorageFile> aFile)
+{
+  mFiles.AppendElement(aFile);
+}
+
+nsresult
+DeviceStorageCursorRequest::SendContinueToParentProcess()
+{
+  if (!NS_IsMainThread()) {
+    nsRefPtr<DeviceStorageCursorRequest> self = this;
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
+    {
+      self->SendContinueToParentProcess();
+    });
+    return NS_DispatchToMainThread(r);
+  }
+
+  MOZ_ASSERT(NS_IsMainThread());
+  DS_LOG_INFO("request parent %u", mId);
+
+  DeviceStorageRequestChild* child
+    = new DeviceStorageRequestChild(this);
+  DeviceStorageGetParams params(mStorageType,
+                                mFile->mStorageName,
+                                mFile->mRootDir,
+                                mFile->mPath);
+  ContentChild::GetSingleton()
+    ->SendPDeviceStorageRequestConstructor(child, params);
+  return NS_OK;
+}
+
+nsresult
+DeviceStorageCursorRequest::Continue()
+{
+  if (!NS_IsMainThread()) {
+    /* The MIME service can only be accessed from the main thread */
+    nsRefPtr<DeviceStorageCursorRequest> self = this;
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
+    {
+      self->Continue();
+    });
+    nsresult rv = NS_DispatchToMainThread(r);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return Reject(POST_ERROR_EVENT_UNKNOWN);
+    }
+    return rv;
+  }
+
+  DS_LOG_INFO("%u", mId);
+
+  nsRefPtr<DeviceStorageFile> file;
+  while (!file && mIndex < mFiles.Length()) {
+    file = mFiles[mIndex].forget();
+    ++mIndex;
+  }
+
+  if (!file) {
+    // No more files remaining, complete cursor
+    return Resolve();
+  }
+
+  file->CalculateMimeType();
+  if (XRE_IsParentProcess()) {
+    return Resolve(file);
+  }
+
+  mFile = file;
+
+  nsresult rv = SendContinueToParentProcess();
+  if (NS_FAILED(rv)) {
+    return Reject(POST_ERROR_EVENT_UNKNOWN);
+  }
+  return rv;
+}
+
+NS_IMETHODIMP
+DeviceStorageCursorRequest::Run()
+{
+  if (mFile->mFile) {
+    bool check;
+    mFile->mFile->IsDirectory(&check);
+    if (!check) {
+      return Reject(POST_ERROR_EVENT_FILE_NOT_ENUMERABLE);
+    }
+  }
+
+  mFile->CollectFiles(mFiles, mSince);
+  return Continue();
+}
+
+nsresult
+DeviceStorageCursorRequest::CreateSendParams(DeviceStorageParams& aParams)
+{
+  DeviceStorageEnumerationParams params(mFile->mStorageType,
+                                        mFile->mStorageName,
+                                        mFile->mRootDir,
+                                        mSince);
+  aParams = params;
+  return NS_OK;
+}
+
+class DeviceStorageCreateFdRequest final
+  : public DeviceStorageRequest
 {
-  MOZ_ASSERT(!mOkToCallContinue);
-  mOkToCallContinue = true;
-}
-
-class PostAvailableResultEvent : public nsRunnable
+public:
+  DeviceStorageCreateFdRequest()
+  {
+    mAccess = DEVICE_STORAGE_ACCESS_CREATE;
+    mUseStreamTransport = true;
+    mCheckFile = true;
+    DS_LOG_INFO("");
+  }
+
+  NS_IMETHOD Run() override
+  {
+    nsString fullPath;
+    mFile->GetFullPath(fullPath);
+    MOZ_ASSERT(!fullPath.IsEmpty());
+
+    bool check = false;
+    mFile->mFile->Exists(&check);
+    if (check) {
+      return Reject(POST_ERROR_EVENT_FILE_EXISTS);
+    }
+
+    nsresult rv = mFile->CreateFileDescriptor(
+                  mDSFileDescriptor->mFileDescriptor);
+
+    if (NS_FAILED(rv)) {
+      mFile->mFile->Remove(false);
+      return Reject(POST_ERROR_EVENT_UNKNOWN);
+    }
+
+    return Resolve(fullPath);
+  }
+
+protected:
+  nsresult CreateSendParams(DeviceStorageParams& aParams) override
+  {
+    DeviceStorageCreateFdParams params;
+    params.type() = mFile->mStorageType;
+    params.storageName() = mFile->mStorageName;
+    params.relpath() = mFile->mPath;
+    aParams = params;
+
+    mFile->Dump("DeviceStorageCreateFdParams");
+    return NS_OK;
+  }
+};
+
+class DeviceStorageCreateRequest final
+  : public DeviceStorageRequest
+{
+public:
+  DeviceStorageCreateRequest()
+  {
+    mAccess = DEVICE_STORAGE_ACCESS_CREATE;
+    mUseStreamTransport = true;
+    DS_LOG_INFO("");
+  }
+
+  NS_IMETHOD Run() override
+  {
+    ErrorResult rv;
+    nsCOMPtr<nsIInputStream> stream;
+    mBlob->GetInternalStream(getter_AddRefs(stream), rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      return Reject(POST_ERROR_EVENT_UNKNOWN);
+    }
+
+    bool check = false;
+    mFile->mFile->Exists(&check);
+    if (check) {
+      return Reject(POST_ERROR_EVENT_FILE_EXISTS);
+    }
+
+    rv = mFile->Write(stream);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      mFile->mFile->Remove(false);
+      return Reject(POST_ERROR_EVENT_UNKNOWN);
+    }
+
+    nsString fullPath;
+    mFile->GetFullPath(fullPath);
+    return Resolve(fullPath);
+  }
+
+protected:
+  nsresult CreateSendParams(DeviceStorageParams& aParams) override
+  {
+    BlobChild* actor
+      = ContentChild::GetSingleton()->GetOrCreateActorForBlobImpl(mBlob);
+    if (!actor) {
+      return NS_ERROR_FAILURE;
+    }
+
+    DeviceStorageAddParams params;
+    params.blobChild() = actor;
+    params.type() = mFile->mStorageType;
+    params.storageName() = mFile->mStorageName;
+    params.relpath() = mFile->mPath;
+    aParams = params;
+    return NS_OK;
+  }
+};
+
+class DeviceStorageAppendRequest final
+  : public DeviceStorageRequest
 {
 public:
-  PostAvailableResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest)
-    : mFile(aFile)
-    , mRequest(aRequest)
+  DeviceStorageAppendRequest()
+  {
+    mAccess = DEVICE_STORAGE_ACCESS_WRITE;
+    mUseStreamTransport = true;
+    DS_LOG_INFO("");
+  }
+
+  NS_IMETHOD Run() override
+  {
+    ErrorResult rv;
+    nsCOMPtr<nsIInputStream> stream;
+    mBlob->GetInternalStream(getter_AddRefs(stream), rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      return Reject(POST_ERROR_EVENT_UNKNOWN);
+    }
+
+    bool check = false;
+    mFile->mFile->Exists(&check);
+    if (!check) {
+      return Reject(POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
+    }
+
+    rv = mFile->Append(stream);
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      return Reject(POST_ERROR_EVENT_UNKNOWN);
+    }
+
+    nsString fullPath;
+    mFile->GetFullPath(fullPath);
+    return Resolve(fullPath);
+  }
+
+protected:
+  nsresult CreateSendParams(DeviceStorageParams& aParams) override
   {
-    MOZ_ASSERT(mRequest);
-  }
-
-  ~PostAvailableResultEvent() {}
-
-  NS_IMETHOD Run()
+    BlobChild* actor
+      = ContentChild::GetSingleton()->GetOrCreateActorForBlobImpl(mBlob);
+    if (!actor) {
+      return NS_ERROR_FAILURE;
+    }
+
+    DeviceStorageAppendParams params;
+    params.blobChild() = actor;
+    params.type() = mFile->mStorageType;
+    params.storageName() = mFile->mStorageName;
+    params.relpath() = mFile->mPath;
+    aParams = params;
+    return NS_OK;
+  }
+};
+
+class DeviceStorageOpenRequest final
+  : public DeviceStorageRequest
+{
+public:
+  using DeviceStorageRequest::Initialize;
+
+  DeviceStorageOpenRequest()
+  {
+    mUseStreamTransport = true;
+    mCheckFile = true;
+    DS_LOG_INFO("");
+  }
+
+  void Initialize(DeviceStorageRequestManager* aManager,
+                  DeviceStorageFile* aFile,
+                  uint32_t aRequest) override
+  {
+    DeviceStorageRequest::Initialize(aManager, aFile, aRequest);
+    mAccess = mFile->mEditable ? DEVICE_STORAGE_ACCESS_WRITE
+                               : DEVICE_STORAGE_ACCESS_READ;
+  }
+
+  NS_IMETHOD Run() override
+  {
+    if (!mFile->mEditable) {
+      bool check = false;
+      mFile->mFile->Exists(&check);
+      if (!check) {
+        return Reject(POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
+      }
+    }
+
+    nsresult rv = mFile->CalculateSizeAndModifiedDate();
+    if (NS_FAILED(rv)) {
+      return Reject(POST_ERROR_EVENT_UNKNOWN);
+    }
+
+    return Resolve(mFile);
+  }
+
+protected:
+  void Prepare() override
   {
     MOZ_ASSERT(NS_IsMainThread());
-    nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
-    if (!window) {
+    mFile->CalculateMimeType();
+  }
+
+  nsresult CreateSendParams(DeviceStorageParams& aParams) override
+  {
+    DeviceStorageGetParams params(mFile->mStorageType,
+                                  mFile->mStorageName,
+                                  mFile->mRootDir,
+                                  mFile->mPath);
+    aParams = params;
+    return NS_OK;
+  }
+};
+
+class DeviceStorageDeleteRequest final
+  : public DeviceStorageRequest
+{
+public:
+  DeviceStorageDeleteRequest()
+  {
+    mAccess = DEVICE_STORAGE_ACCESS_WRITE;
+    mUseStreamTransport = true;
+    mCheckFile = true;
+    DS_LOG_INFO("");
+  }
+
+  NS_IMETHOD Run() override
+  {
+    mFile->Remove();
+    bool check = false;
+    mFile->mFile->Exists(&check);
+    if (check) {
+      return Reject(POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
+    }
+
+    nsString fullPath;
+    mFile->GetFullPath(fullPath);
+    return Resolve(fullPath);
+  }
+
+protected:
+  nsresult CreateSendParams(DeviceStorageParams& aParams) override
+  {
+    DeviceStorageDeleteParams params(mFile->mStorageType,
+                                     mFile->mStorageName,
+                                     mFile->mPath);
+    aParams = params;
+    return NS_OK;
+  }
+};
+
+class DeviceStorageFreeSpaceRequest final
+  : public DeviceStorageRequest
+{
+public:
+  DeviceStorageFreeSpaceRequest()
+  {
+    mAccess = DEVICE_STORAGE_ACCESS_READ;
+    mUseStreamTransport = true;
+    DS_LOG_INFO("");
+  }
+
+  NS_IMETHOD Run() override
+  {
+    int64_t freeSpace = 0;
+    if (mFile) {
+      mFile->GetStorageFreeSpace(&freeSpace);
+    }
+    return Resolve(static_cast<uint64_t>(freeSpace));
+  }
+
+protected:
+  nsresult CreateSendParams(DeviceStorageParams& aParams) override
+  {
+    DeviceStorageFreeSpaceParams params(mFile->mStorageType,
+                                        mFile->mStorageName);
+    aParams = params;
+    return NS_OK;
+  }
+};
+
+class DeviceStorageUsedSpaceRequest final
+  : public DeviceStorageRequest
+{
+public:
+  DeviceStorageUsedSpaceRequest()
+  {
+    mAccess = DEVICE_STORAGE_ACCESS_READ;
+    DS_LOG_INFO("");
+  }
+
+  NS_IMETHOD Run() override
+  {
+    if (mManager->IsOwningThread()) {
+      // this needs to be dispatched to only one (1)
+      // thread or we will do more work than required.
+      DeviceStorageUsedSpaceCache* usedSpaceCache
+        = DeviceStorageUsedSpaceCache::CreateOrGet();
+      MOZ_ASSERT(usedSpaceCache);
+      usedSpaceCache->Dispatch(this);
       return NS_OK;
     }
 
+    uint64_t picturesUsage = 0, videosUsage = 0,
+             musicUsage = 0, totalUsage = 0;
+    mFile->AccumDiskUsage(&picturesUsage, &videosUsage,
+                          &musicUsage, &totalUsage);
+
+    const nsString& type = mFile->mStorageType;
+    if (type.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
+      totalUsage = picturesUsage;
+    } else if (type.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
+      totalUsage = videosUsage;
+    } else if (type.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
+      totalUsage = musicUsage;
+    }
+    return Resolve(totalUsage);
+  }
+
+protected:
+  nsresult CreateSendParams(DeviceStorageParams& aParams) override
+  {
+    DeviceStorageUsedSpaceParams params(mFile->mStorageType,
+                                        mFile->mStorageName);
+    aParams = params;
+    return NS_OK;
+  }
+};
+
+class DeviceStorageAvailableRequest final
+  : public DeviceStorageRequest
+{
+public:
+  DeviceStorageAvailableRequest()
+  {
+    mAccess = DEVICE_STORAGE_ACCESS_READ;
+    DS_LOG_INFO("");
+  }
+
+  NS_IMETHOD Run() override
+  {
     nsString state = NS_LITERAL_STRING("unavailable");
     if (mFile) {
       mFile->GetStatus(state);
     }
-
-    AutoJSContext cx;
-    JS::Rooted<JS::Value> result(cx);
-    StringToJsval(window, state, &result);
-    mRequest->FireSuccess(result);
-    mRequest = nullptr;
+    return Resolve(state);
+  }
+
+protected:
+  nsresult CreateSendParams(DeviceStorageParams& aParams) override
+  {
+    DeviceStorageAvailableParams params(mFile->mStorageType,
+                                        mFile->mStorageName);
+    aParams = params;
     return NS_OK;
   }
-
-private:
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsRefPtr<DOMRequest> mRequest;
 };
 
-class PostStatusResultEvent : public nsRunnable
+class DeviceStorageStatusRequest final
+  : public DeviceStorageRequest
 {
 public:
-  PostStatusResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest)
-    : mFile(aFile)
-    , mRequest(aRequest)
+  DeviceStorageStatusRequest()
   {
-    MOZ_ASSERT(mRequest);
-  }
-
-  ~PostStatusResultEvent() {}
-
-  NS_IMETHOD Run()
+    mAccess = DEVICE_STORAGE_ACCESS_READ;
+    DS_LOG_INFO("");
+  }
+
+  NS_IMETHOD Run() override
   {
-    MOZ_ASSERT(NS_IsMainThread());
-    nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
-    if (!window) {
-      return NS_OK;
-    }
-
     nsString state = NS_LITERAL_STRING("undefined");
     if (mFile) {
       mFile->GetStorageStatus(state);
     }
-
-    AutoJSContext cx;
-    JS::Rooted<JS::Value> result(cx);
-    StringToJsval(window, state, &result);
-    mRequest->FireSuccess(result);
-    mRequest = nullptr;
+    return Resolve(state);
+  }
+
+protected:
+  nsresult CreateSendParams(DeviceStorageParams& aParams) override
+  {
+    DeviceStorageStatusParams params(mFile->mStorageType,
+                                     mFile->mStorageName);
+    aParams = params;
     return NS_OK;
   }
-
-private:
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsRefPtr<DOMRequest> mRequest;
 };
 
-class PostFormatResultEvent : public nsRunnable
+class DeviceStorageWatchRequest final
+  : public DeviceStorageRequest
 {
 public:
-  PostFormatResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest)
-    : mFile(aFile)
-    , mRequest(aRequest)
+  DeviceStorageWatchRequest()
+  {
+    mAccess = DEVICE_STORAGE_ACCESS_READ;
+    mSendToParent = false;
+    DS_LOG_INFO("");
+  }
+
+  NS_IMETHOD Run() override
   {
-    MOZ_ASSERT(mRequest);
-  }
-
-  ~PostFormatResultEvent() {}
-
-  NS_IMETHOD Run()
+    return Resolve();
+  }
+};
+
+class DeviceStorageFormatRequest final
+  : public DeviceStorageRequest
+{
+public:
+  DeviceStorageFormatRequest()
   {
-    MOZ_ASSERT(NS_IsMainThread());
-    nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
-    if (!window) {
-      return NS_OK;
-    }
-
+    mAccess = DEVICE_STORAGE_ACCESS_WRITE;
+    DS_LOG_INFO("");
+  }
+
+  NS_IMETHOD Run() override
+  {
     nsString state = NS_LITERAL_STRING("unavailable");
     if (mFile) {
       mFile->DoFormat(state);
     }
-
-    AutoJSContext cx;
-    JS::Rooted<JS::Value> result(cx);
-    StringToJsval(window, state, &result);
-    mRequest->FireSuccess(result);
-    mRequest = nullptr;
+    return Resolve(state);
+  }
+
+protected:
+  nsresult CreateSendParams(DeviceStorageParams& aParams) override
+  {
+    DeviceStorageFormatParams params(mFile->mStorageType,
+                                     mFile->mStorageName);
+    aParams = params;
     return NS_OK;
   }
-
-private:
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsRefPtr<DOMRequest> mRequest;
 };
 
-class PostMountResultEvent : public nsRunnable
+class DeviceStorageMountRequest final
+  : public DeviceStorageRequest
 {
 public:
-  PostMountResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest)
-    : mFile(aFile)
-    , mRequest(aRequest)
+  DeviceStorageMountRequest()
   {
-    MOZ_ASSERT(mRequest);
-  }
-
-  ~PostMountResultEvent() {}
-
-  NS_IMETHOD Run()
+    mAccess = DEVICE_STORAGE_ACCESS_WRITE;
+    DS_LOG_INFO("");
+  }
+
+  NS_IMETHOD Run() override
   {
-    MOZ_ASSERT(NS_IsMainThread());
-    nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
-    if (!window) {
-      return NS_OK;
-    }
-
     nsString state = NS_LITERAL_STRING("unavailable");
     if (mFile) {
       mFile->DoMount(state);
     }
-
-    AutoJSContext cx;
-    JS::Rooted<JS::Value> result(cx);
-    StringToJsval(window, state, &result);
-    mRequest->FireSuccess(result);
-    mRequest = nullptr;
+    return Resolve(state);
+  }
+
+protected:
+  nsresult CreateSendParams(DeviceStorageParams& aParams) override
+  {
+    DeviceStorageMountParams params(mFile->mStorageType,
+                                    mFile->mStorageName);
+    aParams = params;
     return NS_OK;
   }
-
-private:
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsRefPtr<DOMRequest> mRequest;
 };
 
-class PostUnmountResultEvent : public nsRunnable
+class DeviceStorageUnmountRequest final
+  : public DeviceStorageRequest
 {
 public:
-  PostUnmountResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest)
-    : mFile(aFile)
-    , mRequest(aRequest)
+  DeviceStorageUnmountRequest()
   {
-    MOZ_ASSERT(mRequest);
-  }
-
-  ~PostUnmountResultEvent() {}
-
-  NS_IMETHOD Run()
+    mAccess = DEVICE_STORAGE_ACCESS_WRITE;
+    DS_LOG_INFO("");
+  }
+
+  NS_IMETHOD Run() override
   {
-    MOZ_ASSERT(NS_IsMainThread());
-    nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
-    if (!window) {
-      return NS_OK;
-    }
-
     nsString state = NS_LITERAL_STRING("unavailable");
     if (mFile) {
       mFile->DoUnmount(state);
     }
-
-    AutoJSContext cx;
-    JS::Rooted<JS::Value> result(cx);
-    StringToJsval(window, state, &result);
-    mRequest->FireSuccess(result);
-    mRequest = nullptr;
-    return NS_OK;
-  }
-
-private:
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsRefPtr<DOMRequest> mRequest;
-};
-
-class PostResultEvent : public nsRunnable
-{
-public:
-  PostResultEvent(already_AddRefed<DOMRequest> aRequest,
-                  DeviceStorageFile* aFile)
-    : mFile(aFile)
-    , mRequest(aRequest)
-  {
-    MOZ_ASSERT(mRequest);
-  }
-
-  PostResultEvent(already_AddRefed<DOMRequest> aRequest,
-                  const nsAString & aPath)
-    : mPath(aPath)
-    , mRequest(aRequest)
-  {
-    MOZ_ASSERT(mRequest);
-  }
-
-  PostResultEvent(already_AddRefed<DOMRequest> aRequest,
-                  const uint64_t aValue)
-    : mValue(aValue)
-    , mRequest(aRequest)
-  {
-    MOZ_ASSERT(mRequest);
-  }
-
-  ~PostResultEvent() {}
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
-    if (!window) {
-      return NS_OK;
-    }
-
-    AutoJSContext cx;
-    JS::Rooted<JS::Value> result(cx, JS::NullValue());
-
-    if (mFile) {
-      result = nsIFileToJsval(window, mFile);
-    } else if (mPath.Length()) {
-      StringToJsval(window, mPath, &result);
-    }
-    else {
-      result = JS_NumberValue(double(mValue));
-    }
-
-    mRequest->FireSuccess(result);
-    mRequest = nullptr;
-    return NS_OK;
-  }
-
-private:
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsString mPath;
-  uint64_t mValue;
-  nsRefPtr<DOMRequest> mRequest;
-};
-
-class CreateFdEvent : public nsRunnable
-{
-public:
-  CreateFdEvent(DeviceStorageFileDescriptor* aDSFileDescriptor,
-                already_AddRefed<DOMRequest> aRequest)
-    : mDSFileDescriptor(aDSFileDescriptor)
-    , mRequest(aRequest)
-  {
-    MOZ_ASSERT(mDSFileDescriptor);
-    MOZ_ASSERT(mDSFileDescriptor->mDSFile);
-    MOZ_ASSERT(mDSFileDescriptor->mDSFile->mFile);
-    MOZ_ASSERT(mRequest);
-  }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-
-    DeviceStorageFile* dsFile = mDSFileDescriptor->mDSFile;
-
-    nsString fullPath;
-    dsFile->GetFullPath(fullPath);
-    MOZ_ASSERT(!fullPath.IsEmpty());
-
-    bool check = false;
-    dsFile->mFile->Exists(&check);
-    if (check) {
-      nsCOMPtr<nsIRunnable> event =
-        new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_FILE_EXISTS);
-      return NS_DispatchToMainThread(event);
-    }
-
-    nsresult rv = dsFile->CreateFileDescriptor(mDSFileDescriptor->mFileDescriptor);
-
-    if (NS_FAILED(rv)) {
-      dsFile->mFile->Remove(false);
-
-      nsCOMPtr<nsIRunnable> event =
-        new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN);
-      return NS_DispatchToMainThread(event);
-    }
-
-    nsCOMPtr<nsIRunnable> event =
-      new PostResultEvent(mRequest.forget(), fullPath);
-    return NS_DispatchToMainThread(event);
-  }
-
-private:
-  nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
-  nsRefPtr<DOMRequest> mRequest;
-};
-
-class WriteFileEvent : public nsRunnable
-{
-public:
-  WriteFileEvent(BlobImpl* aBlobImpl,
-                 DeviceStorageFile *aFile,
-                 already_AddRefed<DOMRequest> aRequest,
-                 int32_t aRequestType)
-    : mBlobImpl(aBlobImpl)
-    , mFile(aFile)
-    , mRequest(aRequest)
-    , mRequestType(aRequestType)
-  {
-    MOZ_ASSERT(mFile);
-    MOZ_ASSERT(mFile->mFile);
-    MOZ_ASSERT(mRequest);
-    MOZ_ASSERT(mRequestType);
-  }
-
-  ~WriteFileEvent() {}
-
-  NS_IMETHOD Run()
+    return Resolve(state);
+  }
+
+protected:
+  nsresult CreateSendParams(DeviceStorageParams& aParams) override
   {
-    MOZ_ASSERT(!NS_IsMainThread());
-
-    ErrorResult rv;
-    nsCOMPtr<nsIInputStream> stream;
-    mBlobImpl->GetInternalStream(getter_AddRefs(stream), rv);
-    if (NS_WARN_IF(rv.Failed())) {
-      rv.SuppressException();
-      nsCOMPtr<nsIRunnable> event =
-        new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN);
-      return NS_DispatchToMainThread(event);
-    }
-
-    bool check = false;
-    mFile->mFile->Exists(&check);
-
-    if (mRequestType == DEVICE_STORAGE_REQUEST_APPEND) {
-      if (!check) {
-        nsCOMPtr<nsIRunnable> event =
-          new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
-        return NS_DispatchToMainThread(event);
-      }
-      rv = mFile->Append(stream);
-    }
-    else if (mRequestType == DEVICE_STORAGE_REQUEST_CREATE) {
-      if (check) {
-        nsCOMPtr<nsIRunnable> event =
-          new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_FILE_EXISTS);
-        return NS_DispatchToMainThread(event);
-      }
-      rv = mFile->Write(stream);
-      if (NS_WARN_IF(rv.Failed())) {
-        rv.SuppressException();
-        mFile->mFile->Remove(false);
-      }
-    } else {
-      nsCOMPtr<nsIRunnable> event =
-        new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN);
-      return NS_DispatchToMainThread(event);
-    }
-
-    if (NS_WARN_IF(rv.Failed())) {
-      rv.SuppressException();
-
-      nsCOMPtr<nsIRunnable> event =
-        new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN);
-      return NS_DispatchToMainThread(event);
-    }
-
-    nsString fullPath;
-    mFile->GetFullPath(fullPath);
-    nsCOMPtr<nsIRunnable> event =
-      new PostResultEvent(mRequest.forget(), fullPath);
-    return NS_DispatchToMainThread(event);
-  }
-
-private:
-  nsRefPtr<BlobImpl> mBlobImpl;
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsRefPtr<DOMRequest> mRequest;
-  int32_t mRequestType;
-};
-
-
-class ReadFileEvent : public nsRunnable
-{
-public:
-  ReadFileEvent(DeviceStorageFile* aFile,
-                already_AddRefed<DOMRequest> aRequest)
-    : mFile(aFile)
-    , mRequest(aRequest)
-  {
-    MOZ_ASSERT(mFile);
-    MOZ_ASSERT(mRequest);
-    mFile->CalculateMimeType();
-  }
-
-  ~ReadFileEvent() {}
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-
-    nsCOMPtr<nsIRunnable> r;
-    if (!mFile->mEditable) {
-      bool check = false;
-      mFile->mFile->Exists(&check);
-      if (!check) {
-        r = new PostErrorEvent(mRequest.forget(),
-                               POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
-      }
-    }
-
-    if (!r) {
-      nsresult rv = mFile->CalculateSizeAndModifiedDate();
-      if (NS_FAILED(rv)) {
-        r = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN);
-      }
-    }
-
-    if (!r) {
-      r = new PostResultEvent(mRequest.forget(), mFile);
-    }
-    return NS_DispatchToMainThread(r);
-  }
-
-private:
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsRefPtr<DOMRequest> mRequest;
+    DeviceStorageUnmountParams params(mFile->mStorageType,
+                                      mFile->mStorageName);
+    aParams = params;
+    return NS_OK;
+  }
 };
 
-class DeleteFileEvent : public nsRunnable
-{
-public:
-  DeleteFileEvent(DeviceStorageFile* aFile,
-                  already_AddRefed<DOMRequest> aRequest)
-    : mFile(aFile)
-    , mRequest(aRequest)
-  {
-    MOZ_ASSERT(mFile);
-    MOZ_ASSERT(mRequest);
-  }
-
-  ~DeleteFileEvent() {}
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-    mFile->Remove();
-
-    nsCOMPtr<nsIRunnable> r;
-    bool check = false;
-    mFile->mFile->Exists(&check);
-    if (check) {
-      r = new PostErrorEvent(mRequest.forget(),
-                             POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
-    }
-    else {
-      nsString fullPath;
-      mFile->GetFullPath(fullPath);
-      r = new PostResultEvent(mRequest.forget(), fullPath);
-    }
-    return NS_DispatchToMainThread(r);
-  }
-
-private:
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsRefPtr<DOMRequest> mRequest;
-};
-
-class UsedSpaceFileEvent : public nsRunnable
-{
-public:
-  UsedSpaceFileEvent(DeviceStorageFile* aFile,
-                     already_AddRefed<DOMRequest> aRequest)
-    : mFile(aFile)
-    , mRequest(aRequest)
-  {
-    MOZ_ASSERT(mFile);
-    MOZ_ASSERT(mRequest);
-  }
-
-  ~UsedSpaceFileEvent() {}
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-
-    uint64_t picturesUsage = 0, videosUsage = 0, musicUsage = 0, totalUsage = 0;
-    mFile->AccumDiskUsage(&picturesUsage, &videosUsage,
-                          &musicUsage, &totalUsage);
-    nsCOMPtr<nsIRunnable> r;
-    if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
-      r = new PostResultEvent(mRequest.forget(), picturesUsage);
-    }
-    else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
-      r = new PostResultEvent(mRequest.forget(), videosUsage);
-    }
-    else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
-      r = new PostResultEvent(mRequest.forget(), musicUsage);
-    } else {
-      r = new PostResultEvent(mRequest.forget(), totalUsage);
-    }
-    return NS_DispatchToMainThread(r);
-  }
-
-private:
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsRefPtr<DOMRequest> mRequest;
-};
-
-class FreeSpaceFileEvent : public nsRunnable
-{
-public:
-  FreeSpaceFileEvent(DeviceStorageFile* aFile,
-                     already_AddRefed<DOMRequest> aRequest)
-    : mFile(aFile)
-    , mRequest(aRequest)
-  {
-    MOZ_ASSERT(mFile);
-    MOZ_ASSERT(mRequest);
-  }
-
-  ~FreeSpaceFileEvent() {}
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-
-    int64_t freeSpace = 0;
-    if (mFile) {
-      mFile->GetStorageFreeSpace(&freeSpace);
-    }
-
-    nsCOMPtr<nsIRunnable> r;
-    r = new PostResultEvent(mRequest.forget(),
-                            static_cast<uint64_t>(freeSpace));
-    return NS_DispatchToMainThread(r);
-  }
-
-private:
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsRefPtr<DOMRequest> mRequest;
-};
-
-class DeviceStorageRequest final
+class DeviceStoragePermissionCheck final
   : public nsIContentPermissionRequest
   , public nsIRunnable
 {
 public:
-
-  DeviceStorageRequest(const DeviceStorageRequestType aRequestType,
-                       nsPIDOMWindow* aWindow,
-                       nsIPrincipal* aPrincipal,
-                       DeviceStorageFile* aFile,
-                       DOMRequest* aRequest,
-                       nsDOMDeviceStorage* aDeviceStorage)
-    : mRequestType(aRequestType)
-    , mWindow(aWindow)
-    , mPrincipal(aPrincipal)
-    , mFile(aFile)
-    , mRequest(aRequest)
-    , mDeviceStorage(aDeviceStorage)
-    , mRequester(new nsContentPermissionRequester(mWindow))
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStoragePermissionCheck,
+                                           nsIContentPermissionRequest)
+
+  DeviceStoragePermissionCheck(DeviceStorageRequest* aRequest,
+                               uint64_t aWindowID,
+                               const PrincipalInfo &aPrincipalInfo)
+    : mRequest(aRequest)
+    , mWindowID(aWindowID)
+    , mPrincipalInfo(aPrincipalInfo)
   {
-    MOZ_ASSERT(mWindow);
-    MOZ_ASSERT(mPrincipal);
-    MOZ_ASSERT(mFile);
     MOZ_ASSERT(mRequest);
-    MOZ_ASSERT(mDeviceStorage);
-  }
-
-  DeviceStorageRequest(const DeviceStorageRequestType aRequestType,
-                       nsPIDOMWindow* aWindow,
-                       nsIPrincipal* aPrincipal,
-                       DeviceStorageFile* aFile,
-                       DOMRequest* aRequest,
-                       Blob* aBlob = nullptr)
-    : mRequestType(aRequestType)
-    , mWindow(aWindow)
-    , mPrincipal(aPrincipal)
-    , mFile(aFile)
-    , mRequest(aRequest)
-    , mBlob(aBlob)
-    , mRequester(new nsContentPermissionRequester(mWindow))
-  {
-    MOZ_ASSERT(mWindow);
-    MOZ_ASSERT(mPrincipal);
-    MOZ_ASSERT(mFile);
-    MOZ_ASSERT(mRequest);
-  }
-
-  DeviceStorageRequest(const DeviceStorageRequestType aRequestType,
-                       nsPIDOMWindow* aWindow,
-                       nsIPrincipal* aPrincipal,
-                       DeviceStorageFile* aFile,
-                       DOMRequest* aRequest,
-                       DeviceStorageFileDescriptor* aDSFileDescriptor)
-    : mRequestType(aRequestType)
-    , mWindow(aWindow)
-    , mPrincipal(aPrincipal)
-    , mFile(aFile)
-    , mRequest(aRequest)
-    , mDSFileDescriptor(aDSFileDescriptor)
-    , mRequester(new nsContentPermissionRequester(mWindow))
-  {
-    MOZ_ASSERT(mRequestType == DEVICE_STORAGE_REQUEST_CREATEFD);
-    MOZ_ASSERT(mWindow);
-    MOZ_ASSERT(mPrincipal);
-    MOZ_ASSERT(mFile);
-    MOZ_ASSERT(mRequest);
-    MOZ_ASSERT(mDSFileDescriptor);
-  }
-
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStorageRequest,
-                                           nsIContentPermissionRequest)
+  }
 
   NS_IMETHOD Run() override
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (DeviceStorageStatics::IsPromptTesting()) {
-      Allow(JS::UndefinedHandleValue);
-      return NS_OK;
+      return Allow(JS::UndefinedHandleValue);
+    }
+
+    mWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
+    if (NS_WARN_IF(!mWindow)) {
+      return Cancel();
+    }
+
+    nsresult rv;
+    mPrincipal = PrincipalInfoToPrincipal(mPrincipalInfo, &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return Cancel();
     }
 
+    mRequester = new nsContentPermissionRequester(mWindow);
     return nsContentPermissionUtils::AskPermission(this, mWindow);
   }
 
+  NS_IMETHOD Cancel() override
+  {
+    return Resolve(false);
+  }
+
+  NS_IMETHOD Allow(JS::HandleValue aChoices) override
+  {
+    MOZ_ASSERT(aChoices.isUndefined());
+    return Resolve(true);
+  }
+
   NS_IMETHODIMP GetTypes(nsIArray** aTypes) override
   {
+    nsString storageType;
+    mRequest->GetStorageType(storageType);
     nsCString type;
     nsresult rv =
-      DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, type);
-    if (NS_FAILED(rv)) {
+      DeviceStorageTypeChecker::GetPermissionForType(storageType, type);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     nsCString access;
-    rv = DeviceStorageTypeChecker::GetAccessForRequest(
-      DeviceStorageRequestType(mRequestType), access);
-    if (NS_FAILED(rv)) {
+    rv = DeviceStorageTypeChecker::GetAccessForIndex(mRequest->GetAccess(),
+                                                     access);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     nsTArray<nsString> emptyOptions;
     return nsContentPermissionUtils::CreatePermissionArray(type, access, emptyOptions, aTypes);
   }
 
+  NS_IMETHOD GetRequester(nsIContentPermissionRequester** aRequester) override
+  {
+    NS_ENSURE_ARG_POINTER(aRequester);
+
+    nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+    requester.forget(aRequester);
+    return NS_OK;
+  }
+
   NS_IMETHOD GetPrincipal(nsIPrincipal * *aRequestingPrincipal) override
   {
     NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal);
     return NS_OK;
   }
 
   NS_IMETHOD GetWindow(nsIDOMWindow * *aRequestingWindow) override
   {
@@ -2506,400 +2410,49 @@ public:
   }
 
   NS_IMETHOD GetElement(nsIDOMElement * *aRequestingElement) override
   {
     *aRequestingElement = nullptr;
     return NS_OK;
   }
 
-  NS_IMETHOD Cancel() override
+private:
+  nsresult Resolve(bool aResolve)
   {
-    nsCOMPtr<nsIRunnable> event
-      = new PostErrorEvent(mRequest.forget(),
-                           POST_ERROR_EVENT_PERMISSION_DENIED);
-    return NS_DispatchToMainThread(event);
-  }
-
-  NS_IMETHOD Allow(JS::HandleValue aChoices) override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(aChoices.isUndefined());
-
-    if (!mRequest) {
-      return NS_ERROR_FAILURE;
+    mRequest->GetManager()->StorePermission(mRequest->GetAccess(), aResolve);
+    mRequest->PermissionCacheMissed();
+    if (aResolve) {
+      return mRequest->Allow();
     }
-
-    nsCOMPtr<nsIRunnable> r;
-
-    switch(mRequestType) {
-      case DEVICE_STORAGE_REQUEST_CREATEFD:
-      {
-        if (!mFile->mFile) {
-          return NS_ERROR_FAILURE;
-        }
-
-        DeviceStorageTypeChecker* typeChecker
-          = DeviceStorageTypeChecker::CreateOrGet();
-        if (!typeChecker) {
-          return NS_OK;
-        }
-
-        if (!typeChecker->Check(mFile->mStorageType, mFile->mFile)) {
-          r = new PostErrorEvent(mRequest.forget(),
-                                 POST_ERROR_EVENT_ILLEGAL_TYPE);
-          return NS_DispatchToCurrentThread(r);
-        }
-
-        if (!XRE_IsParentProcess()) {
-
-          DeviceStorageCreateFdParams params;
-          params.type() = mFile->mStorageType;
-          params.storageName() = mFile->mStorageName;
-          params.relpath() = mFile->mPath;
-
-          mFile->Dump("DeviceStorageCreateFdParams");
-
-          PDeviceStorageRequestChild* child
-            = new DeviceStorageRequestChild(mRequest, mFile,
-                                            mDSFileDescriptor.get());
-          ContentChild::GetSingleton()
-            ->SendPDeviceStorageRequestConstructor(child, params);
-          return NS_OK;
-        }
-        mDSFileDescriptor->mDSFile = mFile;
-        r = new CreateFdEvent(mDSFileDescriptor.get(), mRequest.forget());
-        break;
-      }
-
-      case DEVICE_STORAGE_REQUEST_CREATE:
-      {
-        if (!mBlob || !mFile->mFile) {
-          return NS_ERROR_FAILURE;
-        }
-
-        DeviceStorageTypeChecker* typeChecker
-          = DeviceStorageTypeChecker::CreateOrGet();
-        if (!typeChecker) {
-          return NS_OK;
-        }
-
-        if (!typeChecker->Check(mFile->mStorageType, mFile->mFile) ||
-            !typeChecker->Check(mFile->mStorageType, mBlob)) {
-          r = new PostErrorEvent(mRequest.forget(),
-                                 POST_ERROR_EVENT_ILLEGAL_TYPE);
-          return NS_DispatchToCurrentThread(r);
-        }
-
-        if (!XRE_IsParentProcess()) {
-          BlobChild* actor
-            = ContentChild::GetSingleton()->GetOrCreateActorForBlob(
-              static_cast<Blob*>(mBlob.get()));
-          if (!actor) {
-            return NS_ERROR_FAILURE;
-          }
-
-          DeviceStorageAddParams params;
-          params.blobChild() = actor;
-          params.type() = mFile->mStorageType;
-          params.storageName() = mFile->mStorageName;
-          params.relpath() = mFile->mPath;
-
-          PDeviceStorageRequestChild* child
-            = new DeviceStorageRequestChild(mRequest, mFile);
-          ContentChild::GetSingleton()
-            ->SendPDeviceStorageRequestConstructor(child, params);
-          return NS_OK;
-        }
-
-        File* blob = static_cast<File*>(mBlob.get());
-        r = new WriteFileEvent(blob->Impl(), mFile, mRequest.forget(),
-                               mRequestType);
-        break;
-      }
-
-      case DEVICE_STORAGE_REQUEST_APPEND:
-      {
-        if (!mBlob || !mFile->mFile) {
-          return NS_ERROR_FAILURE;
-        }
-
-        DeviceStorageTypeChecker* typeChecker
-          = DeviceStorageTypeChecker::CreateOrGet();
-        if (!typeChecker) {
-          return NS_OK;
-        }
-
-        if (!typeChecker->Check(mFile->mStorageType, mFile->mFile) ||
-            !typeChecker->Check(mFile->mStorageType, mBlob)) {
-          r = new PostErrorEvent(mRequest.forget(),
-                                 POST_ERROR_EVENT_ILLEGAL_TYPE);
-          return NS_DispatchToCurrentThread(r);
-        }
-
-        if (!XRE_IsParentProcess()) {
-          BlobChild* actor
-            = ContentChild::GetSingleton()->GetOrCreateActorForBlob(
-              static_cast<Blob*>(mBlob.get()));
-          if (!actor) {
-            return NS_ERROR_FAILURE;
-          }
-
-          DeviceStorageAppendParams params;
-          params.blobChild() = actor;
-          params.type() = mFile->mStorageType;
-          params.storageName() = mFile->mStorageName;
-          params.relpath() = mFile->mPath;
-
-          PDeviceStorageRequestChild* child
-            = new DeviceStorageRequestChild(mRequest, mFile);
-          ContentChild::GetSingleton()
-            ->SendPDeviceStorageRequestConstructor(child, params);
-          return NS_OK;
-        }
-
-        File* blob = static_cast<File*>(mBlob.get());
-        r = new WriteFileEvent(blob->Impl(), mFile, mRequest.forget(),
-                               mRequestType);
-        break;
-      }
-
-      case DEVICE_STORAGE_REQUEST_READ:
-      case DEVICE_STORAGE_REQUEST_WRITE:
-      {
-        if (!mFile->mFile) {
-          return NS_ERROR_FAILURE;
-        }
-
-        DeviceStorageTypeChecker* typeChecker
-          = DeviceStorageTypeChecker::CreateOrGet();
-        if (!typeChecker) {
-          return NS_OK;
-        }
-
-        if (!typeChecker->Check(mFile->mStorageType, mFile->mFile)) {
-          r = new PostErrorEvent(mRequest.forget(),
-                                 POST_ERROR_EVENT_ILLEGAL_TYPE);
-          return NS_DispatchToCurrentThread(r);
-        }
-
-        if (!XRE_IsParentProcess()) {
-          PDeviceStorageRequestChild* child
-            = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageGetParams params(mFile->mStorageType,
-                                        mFile->mStorageName,
-                                        mFile->mRootDir,
-                                        mFile->mPath);
-          ContentChild::GetSingleton()
-            ->SendPDeviceStorageRequestConstructor(child, params);
-          return NS_OK;
-        }
-
-        r = new ReadFileEvent(mFile, mRequest.forget());
-        break;
-      }
-
-      case DEVICE_STORAGE_REQUEST_DELETE:
-      {
-        if (!mFile->mFile) {
-          return NS_ERROR_FAILURE;
-        }
-
-        DeviceStorageTypeChecker* typeChecker
-          = DeviceStorageTypeChecker::CreateOrGet();
-        if (!typeChecker) {
-          return NS_OK;
-        }
-
-        if (!typeChecker->Check(mFile->mStorageType, mFile->mFile)) {
-          r = new PostErrorEvent(mRequest.forget(),
-                                 POST_ERROR_EVENT_ILLEGAL_TYPE);
-          return NS_DispatchToCurrentThread(r);
-        }
-
-        if (!XRE_IsParentProcess()) {
-          PDeviceStorageRequestChild* child
-            = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageDeleteParams params(mFile->mStorageType,
-                                           mFile->mStorageName,
-                                           mFile->mPath);
-          ContentChild::GetSingleton()
-            ->SendPDeviceStorageRequestConstructor(child, params);
-          return NS_OK;
-        }
-        r = new DeleteFileEvent(mFile, mRequest.forget());
-        break;
-      }
-
-      case DEVICE_STORAGE_REQUEST_FREE_SPACE:
-      {
-        if (!XRE_IsParentProcess()) {
-          PDeviceStorageRequestChild* child
-            = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageFreeSpaceParams params(mFile->mStorageType,
-                                              mFile->mStorageName);
-          ContentChild::GetSingleton()
-            ->SendPDeviceStorageRequestConstructor(child, params);
-          return NS_OK;
-        }
-        r = new FreeSpaceFileEvent(mFile, mRequest.forget());
-        break;
-      }
-
-      case DEVICE_STORAGE_REQUEST_USED_SPACE:
-      {
-        if (!XRE_IsParentProcess()) {
-          PDeviceStorageRequestChild* child
-            = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageUsedSpaceParams params(mFile->mStorageType,
-                                              mFile->mStorageName);
-          ContentChild::GetSingleton()
-            ->SendPDeviceStorageRequestConstructor(child, params);
-          return NS_OK;
-        }
-        // this needs to be dispatched to only one (1)
-        // thread or we will do more work than required.
-        DeviceStorageUsedSpaceCache* usedSpaceCache
-          = DeviceStorageUsedSpaceCache::CreateOrGet();
-        MOZ_ASSERT(usedSpaceCache);
-        r = new UsedSpaceFileEvent(mFile, mRequest.forget());
-        usedSpaceCache->Dispatch(r);
-        return NS_OK;
-      }
-
-      case DEVICE_STORAGE_REQUEST_AVAILABLE:
-      {
-        if (!XRE_IsParentProcess()) {
-          PDeviceStorageRequestChild* child
-            = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageAvailableParams params(mFile->mStorageType,
-                                              mFile->mStorageName);
-          ContentChild::GetSingleton()
-            ->SendPDeviceStorageRequestConstructor(child, params);
-          return NS_OK;
-        }
-        r = new PostAvailableResultEvent(mFile, mRequest);
-        return NS_DispatchToCurrentThread(r);
-      }
-
-      case DEVICE_STORAGE_REQUEST_STATUS:
-      {
-        if (!XRE_IsParentProcess()) {
-          PDeviceStorageRequestChild* child
-            = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageStatusParams params(mFile->mStorageType,
-                                              mFile->mStorageName);
-          ContentChild::GetSingleton()
-            ->SendPDeviceStorageRequestConstructor(child, params);
-          return NS_OK;
-        }
-        r = new PostStatusResultEvent(mFile, mRequest);
-        return NS_DispatchToCurrentThread(r);
-      }
-
-      case DEVICE_STORAGE_REQUEST_WATCH:
-      {
-        mDeviceStorage->mAllowedToWatchFile = true;
-        return NS_OK;
-      }
-
-      case DEVICE_STORAGE_REQUEST_FORMAT:
-      {
-        if (!XRE_IsParentProcess()) {
-          PDeviceStorageRequestChild* child
-            = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageFormatParams params(mFile->mStorageType,
-                                           mFile->mStorageName);
-          ContentChild::GetSingleton()
-            ->SendPDeviceStorageRequestConstructor(child, params);
-          return NS_OK;
-        }
-        r = new PostFormatResultEvent(mFile, mRequest);
-        return NS_DispatchToCurrentThread(r);
-      }
-
-      case DEVICE_STORAGE_REQUEST_MOUNT:
-      {
-        if (!XRE_IsParentProcess()) {
-          PDeviceStorageRequestChild* child
-            = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageMountParams params(mFile->mStorageType,
-                                           mFile->mStorageName);
-          ContentChild::GetSingleton()
-            ->SendPDeviceStorageRequestConstructor(child, params);
-          return NS_OK;
-        }
-        r = new PostMountResultEvent(mFile, mRequest);
-        return NS_DispatchToCurrentThread(r);
-      }
-
-      case DEVICE_STORAGE_REQUEST_UNMOUNT:
-      {
-        if (!XRE_IsParentProcess()) {
-          PDeviceStorageRequestChild* child
-            = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageUnmountParams params(mFile->mStorageType,
-                                           mFile->mStorageName);
-          ContentChild::GetSingleton()
-            ->SendPDeviceStorageRequestConstructor(child, params);
-          return NS_OK;
-        }
-        r = new PostUnmountResultEvent(mFile, mRequest);
-        return NS_DispatchToCurrentThread(r);
-      }
-    }
-
-    if (r) {
-      nsCOMPtr<nsIEventTarget> target
-        = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
-      MOZ_ASSERT(target);
-      target->Dispatch(r, NS_DISPATCH_NORMAL);
-    }
-
-    return NS_OK;
-  }
-
-  NS_IMETHOD GetRequester(nsIContentPermissionRequester** aRequester) override
-  {
-    NS_ENSURE_ARG_POINTER(aRequester);
-
-    nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
-    requester.forget(aRequester);
-    return NS_OK;
-  }
-
-private:
-  ~DeviceStorageRequest() {}
-
-  int32_t mRequestType;
+    return mRequest->Cancel();
+  }
+
+  virtual ~DeviceStoragePermissionCheck()
+  { }
+
+  nsRefPtr<DeviceStorageRequest> mRequest;
+  uint64_t mWindowID;
+  PrincipalInfo mPrincipalInfo;
   nsCOMPtr<nsPIDOMWindow> mWindow;
   nsCOMPtr<nsIPrincipal> mPrincipal;
-  nsRefPtr<DeviceStorageFile> mFile;
-
-  nsRefPtr<DOMRequest> mRequest;
-  nsRefPtr<Blob> mBlob;
-  nsRefPtr<nsDOMDeviceStorage> mDeviceStorage;
-  nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
   nsCOMPtr<nsIContentPermissionRequester> mRequester;
 };
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeviceStorageRequest)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeviceStoragePermissionCheck)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
   NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
   NS_INTERFACE_MAP_ENTRY(nsIRunnable)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(DeviceStorageRequest)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(DeviceStorageRequest)
-
-NS_IMPL_CYCLE_COLLECTION(DeviceStorageRequest,
-                         mRequest,
-                         mWindow,
-                         mBlob,
-                         mDeviceStorage)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DeviceStoragePermissionCheck)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DeviceStoragePermissionCheck)
+
+NS_IMPL_CYCLE_COLLECTION(DeviceStoragePermissionCheck,
+                         mWindow)
 
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMDeviceStorage)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   /* nsISupports is an ambiguous base of nsDOMDeviceStorage
      so we have to work around that. */
   if ( aIID.Equals(NS_GET_IID(nsDOMDeviceStorage)) )
     foundInterface = static_cast<nsISupports*>(static_cast<void*>(this));
@@ -2910,51 +2463,103 @@ NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStor
 NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorage, DOMEventTargetHelper)
 
 int nsDOMDeviceStorage::sInstanceCount = 0;
 
 nsDOMDeviceStorage::nsDOMDeviceStorage(nsPIDOMWindow* aWindow)
   : DOMEventTargetHelper(aWindow)
   , mIsShareable(false)
   , mIsRemovable(false)
-  , mIsWatchingFile(false)
-  , mAllowedToWatchFile(false)
+  , mInnerWindowID(0)
+  , mOwningThread(NS_GetCurrentThread())
+{
+  MOZ_ASSERT(NS_IsMainThread()); // worker support incomplete
+  sInstanceCount++;
+  DS_LOG_DEBUG("%p (%d)", this, sInstanceCount);
+}
+
+nsresult
+nsDOMDeviceStorage::CheckPermission(DeviceStorageRequest* aRequest)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  sInstanceCount++;
+  MOZ_ASSERT(mManager);
+  uint32_t cache = mManager->CheckPermission(aRequest->GetAccess());
+  switch (cache) {
+    case nsIPermissionManager::ALLOW_ACTION:
+      return aRequest->Allow();
+    case nsIPermissionManager::DENY_ACTION:
+      return aRequest->Cancel();
+    case nsIPermissionManager::PROMPT_ACTION:
+    default:
+    {
+      nsCOMPtr<nsIThread> mainThread;
+      nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return aRequest->Reject(POST_ERROR_EVENT_UNKNOWN);
+      }
+
+      /* We need to do a bit of a song and dance here to release the object
+         because while we can initially increment the ownership count (no one
+         else is using it), we cannot safely decrement after dispatching because
+         it uses cycle collection and requires the main thread to free it. */
+      nsCOMPtr<nsIRunnable> r
+        = new DeviceStoragePermissionCheck(aRequest, mInnerWindowID,
+                                           *mPrincipalInfo);
+      rv = mainThread->Dispatch(r, NS_DISPATCH_NORMAL);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        rv = aRequest->Reject(POST_ERROR_EVENT_UNKNOWN);
+      }
+      NS_ProxyRelease(mainThread, r.forget().take());
+      return rv;
+    }
+  }
+}
+
+bool
+nsDOMDeviceStorage::IsOwningThread()
+{
+  bool owner = false;
+  mOwningThread->IsOnCurrentThread(&owner);
+  return owner;
+}
+
+nsresult
+nsDOMDeviceStorage::DispatchToOwningThread(nsIRunnable* aRunnable)
+{
+  return mOwningThread->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
 }
 
 /* virtual */ JSObject*
 nsDOMDeviceStorage::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return DeviceStorageBinding::Wrap(aCx, this, aGivenProto);
 }
 
 nsresult
 nsDOMDeviceStorage::Init(nsPIDOMWindow* aWindow, const nsAString &aType,
                          const nsAString &aVolName)
 {
   MOZ_ASSERT(aWindow);
+  mInnerWindowID = aWindow->WindowID();
 
   SetRootDirectoryForType(aType, aVolName);
   if (!mRootDirectory) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  nsresult rv;
   DeviceStorageStatics::AddListener(this);
   if (!mStorageName.IsEmpty()) {
     mIsDefaultLocation = Default();
 
 #ifdef MOZ_WIDGET_GONK
     if (DeviceStorageTypeChecker::IsVolumeBased(mStorageType)) {
       nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
       if (NS_WARN_IF(!vs)) {
         return NS_ERROR_FAILURE;
       }
-      nsresult rv;
       nsCOMPtr<nsIVolume> vol;
       rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
       bool isFake;
       rv = vol->GetIsFake(&isFake);
       if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -2966,55 +2571,86 @@ nsDOMDeviceStorage::Init(nsPIDOMWindow* 
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
       mIsRemovable = isRemovable;
     }
 #endif
   }
 
+  nsCOMPtr<nsIPrincipal> principal;
+  rv = CheckPrincipal(aWindow, aType.EqualsLiteral(DEVICESTORAGE_APPS), getter_AddRefs(principal));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mPrincipalInfo = new PrincipalInfo();
+  rv = PrincipalToPrincipalInfo(principal, mPrincipalInfo);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mManager = new DeviceStorageRequestManager();
+  DS_LOG_DEBUG("%p owns %p", this, mManager.get());
+  return NS_OK;
+}
+
+nsDOMDeviceStorage::~nsDOMDeviceStorage()
+{
+  DS_LOG_DEBUG("%p (%d)", this, sInstanceCount);
+  MOZ_ASSERT(IsOwningThread());
+  sInstanceCount--;
+  DeviceStorageStatics::RemoveListener(this);
+}
+
+// static
+nsresult
+nsDOMDeviceStorage::CheckPrincipal(nsPIDOMWindow* aWindow, bool aIsAppsStorage, nsIPrincipal** aPrincipal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aWindow);
+
   // Grab the principal of the document
   nsCOMPtr<nsIDocument> doc = aWindow->GetDoc();
   if (!doc) {
     return NS_ERROR_FAILURE;
   }
-  mPrincipal = doc->NodePrincipal();
+  nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
 
   // the 'apps' type is special.  We only want this exposed
   // if the caller has the "webapps-manage" permission.
-  if (aType.EqualsLiteral(DEVICESTORAGE_APPS)) {
+  if (aIsAppsStorage) {
     nsCOMPtr<nsIPermissionManager> permissionManager
       = services::GetPermissionManager();
     NS_ENSURE_TRUE(permissionManager, NS_ERROR_FAILURE);
 
     uint32_t permission;
     nsresult rv
-      = permissionManager->TestPermissionFromPrincipal(mPrincipal,
+      = permissionManager->TestPermissionFromPrincipal(principal,
                                                        "webapps-manage",
                                                        &permission);
 
     if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) {
       return NS_ERROR_NOT_AVAILABLE;
     }
   }
 
+  principal.forget(aPrincipal);
   return NS_OK;
 }
 
-nsDOMDeviceStorage::~nsDOMDeviceStorage()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  sInstanceCount--;
-  DeviceStorageStatics::RemoveListener(this);
-}
-
 void
 nsDOMDeviceStorage::Shutdown()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(IsOwningThread());
+
+  if (mManager) {
+    mManager->Shutdown();
+    mManager = nullptr;
+  }
 
   if (mFileSystem) {
     mFileSystem->Shutdown();
     mFileSystem = nullptr;
   }
 
   DeviceStorageStatics::RemoveListener(this);
 }
@@ -3137,18 +2773,17 @@ nsDOMDeviceStorage::CreateDeviceStorageB
 
 bool
 nsDOMDeviceStorage::Equals(nsPIDOMWindow* aWin,
                            const nsAString& aName,
                            const nsAString& aType)
 {
   MOZ_ASSERT(aWin);
 
-  nsCOMPtr<nsPIDOMWindow> window = GetOwner();
-  return aWin && aWin == window &&
+  return aWin && aWin->WindowID() == mInnerWindowID &&
          mStorageName.Equals(aName) &&
          mStorageType.Equals(aType);
 }
 
 // static
 bool
 nsDOMDeviceStorage::ParseFullPath(const nsAString& aFullPath,
                                   nsAString& aOutStorageName,
@@ -3194,17 +2829,17 @@ nsDOMDeviceStorage::GetStorage(const nsA
     ds = GetStorageByName(storageName);
   }
   return ds.forget();
 }
 
 already_AddRefed<nsDOMDeviceStorage>
 nsDOMDeviceStorage::GetStorageByName(const nsAString& aStorageName)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(IsOwningThread());
 
   nsRefPtr<nsDOMDeviceStorage> ds;
 
   if (mStorageName.Equals(aStorageName)) {
     ds = this;
     return ds.forget();
   }
 
@@ -3325,447 +2960,389 @@ nsDOMDeviceStorage::Add(Blob* aBlob, Err
 
   return AddNamed(aBlob, NS_ConvertASCIItoUTF16(path), aRv);
 }
 
 already_AddRefed<DOMRequest>
 nsDOMDeviceStorage::AddNamed(Blob* aBlob, const nsAString& aPath,
                              ErrorResult& aRv)
 {
-  return AddOrAppendNamed(aBlob, aPath,
-                          DEVICE_STORAGE_REQUEST_CREATE, aRv);
+  return AddOrAppendNamed(aBlob, aPath, true, aRv);
 }
 
 already_AddRefed<DOMRequest>
 nsDOMDeviceStorage::AppendNamed(Blob* aBlob, const nsAString& aPath,
                                 ErrorResult& aRv)
 {
-  return AddOrAppendNamed(aBlob, aPath,
-                          DEVICE_STORAGE_REQUEST_APPEND, aRv);
+  return AddOrAppendNamed(aBlob, aPath, false, aRv);
 }
 
+uint32_t
+nsDOMDeviceStorage::CreateDOMRequest(DOMRequest** aRequest, ErrorResult& aRv)
+{
+  if (!mManager) {
+    DS_LOG_WARN("shutdown");
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return DeviceStorageRequestManager::INVALID_ID;
+  }
+
+  return mManager->Create(this, aRequest);
+}
+
+uint32_t
+nsDOMDeviceStorage::CreateDOMCursor(DeviceStorageCursorRequest* aRequest, nsDOMDeviceStorageCursor** aCursor, ErrorResult& aRv)
+{
+  if (!mManager) {
+    DS_LOG_WARN("shutdown");
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return DeviceStorageRequestManager::INVALID_ID;
+  }
+
+  return mManager->Create(this, aRequest, aCursor);
+}
 
 already_AddRefed<DOMRequest>
-nsDOMDeviceStorage::AddOrAppendNamed(Blob* aBlob, const nsAString& aPath,
-                                     const int32_t aRequestType, ErrorResult& aRv)
+nsDOMDeviceStorage::CreateAndRejectDOMRequest(const char *aReason, ErrorResult& aRv)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // if the blob is null here, bail
-  if (!aBlob) {
-    return nullptr;
-  }
-
-  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
-  if (!win) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  DeviceStorageTypeChecker* typeChecker
-    = DeviceStorageTypeChecker::CreateOrGet();
-  if (!typeChecker) {
-    aRv.Throw(NS_ERROR_FAILURE);
+  nsRefPtr<DOMRequest> request;
+  uint32_t id = CreateDOMRequest(getter_AddRefs(request), aRv);
+  if (aRv.Failed()) {
     return nullptr;
   }
 
-  nsCOMPtr<nsIRunnable> r;
-  nsresult rv;
-
-  if (IsFullPath(aPath)) {
-    nsString storagePath;
-    nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
-    if (!ds) {
-      nsRefPtr<DOMRequest> request = new DOMRequest(win);
-      r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
-      rv = NS_DispatchToCurrentThread(r);
-      if (NS_FAILED(rv)) {
-        aRv.Throw(rv);
-      }
-      return request.forget();
-    }
-
-    return ds->AddOrAppendNamed(aBlob, storagePath,
-                                aRequestType, aRv);
-  }
-
-  nsRefPtr<DOMRequest> request = new DOMRequest(win);
-
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
-                                                          mStorageName,
-                                                          aPath);
-  if (!dsf->IsSafePath()) {
-    r = new PostErrorEvent(request, POST_ERROR_EVENT_PERMISSION_DENIED);
-  } else if (!typeChecker->Check(mStorageType, dsf->mFile) ||
-      !typeChecker->Check(mStorageType, aBlob)) {
-    r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_TYPE);
-  } else if (aRequestType == DEVICE_STORAGE_REQUEST_APPEND ||
-             aRequestType == DEVICE_STORAGE_REQUEST_CREATE) {
-    r = new DeviceStorageRequest(DeviceStorageRequestType(aRequestType),
-                                 win, mPrincipal, dsf, request, aBlob);
-  } else {
-      aRv.Throw(NS_ERROR_UNEXPECTED);
-      return nullptr;
-  }
-
-  rv = NS_DispatchToCurrentThread(r);
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-  }
+  aRv = mManager->Reject(id, aReason);
   return request.forget();
 }
 
 already_AddRefed<DOMRequest>
-nsDOMDeviceStorage::GetInternal(const nsAString& aPath, bool aEditable,
-                                ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
-  if (!win) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  nsRefPtr<DOMRequest> request = new DOMRequest(win);
-
-  if (IsFullPath(aPath)) {
-    nsString storagePath;
-    nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
-    if (!ds) {
-      nsCOMPtr<nsIRunnable> r =
-        new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
-      nsresult rv = NS_DispatchToCurrentThread(r);
-      if (NS_FAILED(rv)) {
-        aRv.Throw(rv);
-      }
-      return request.forget();
-    }
-    ds->GetInternal(win, storagePath, request, aEditable);
-    return request.forget();
-  }
-  GetInternal(win, aPath, request, aEditable);
-  return request.forget();
-}
-
-void
-nsDOMDeviceStorage::GetInternal(nsPIDOMWindow *aWin,
-                                const nsAString& aPath,
-                                DOMRequest* aRequest,
-                                bool aEditable)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
-                                                          mStorageName,
-                                                          aPath);
-  dsf->SetEditable(aEditable);
-
-  nsCOMPtr<nsIRunnable> r;
-  if (!dsf->IsSafePath()) {
-    r = new PostErrorEvent(aRequest, POST_ERROR_EVENT_PERMISSION_DENIED);
-  } else {
-    r = new DeviceStorageRequest(aEditable ? DEVICE_STORAGE_REQUEST_WRITE
-                                           : DEVICE_STORAGE_REQUEST_READ,
-                                 aWin, mPrincipal, dsf, aRequest);
-  }
-  DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(r);
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-}
-
-already_AddRefed<DOMRequest>
-nsDOMDeviceStorage::Delete(const nsAString& aPath, ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
-  if (!win) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  nsRefPtr<DOMRequest> request = new DOMRequest(win);
-
-  if (IsFullPath(aPath)) {
-    nsString storagePath;
-    nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
-    if (!ds) {
-      nsCOMPtr<nsIRunnable> r =
-        new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
-      nsresult rv = NS_DispatchToCurrentThread(r);
-      if (NS_FAILED(rv)) {
-        aRv.Throw(rv);
-      }
-      return request.forget();
-    }
-    ds->DeleteInternal(win, storagePath, request);
-    return request.forget();
-  }
-  DeleteInternal(win, aPath, request);
-  return request.forget();
-}
-
-void
-nsDOMDeviceStorage::DeleteInternal(nsPIDOMWindow *aWin,
-                                   const nsAString& aPath,
-                                   DOMRequest* aRequest)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsIRunnable> r;
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
-                                                          mStorageName,
-                                                          aPath);
-  if (!dsf->IsSafePath()) {
-    r = new PostErrorEvent(aRequest, POST_ERROR_EVENT_PERMISSION_DENIED);
-  } else {
-    r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_DELETE,
-                                 aWin, mPrincipal, dsf, aRequest);
-  }
-  DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(r);
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
-}
-
-already_AddRefed<DOMRequest>
-nsDOMDeviceStorage::FreeSpace(ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
-  if (!win) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  nsRefPtr<DOMRequest> request = new DOMRequest(win);
-
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
-                                                          mStorageName);
-  nsCOMPtr<nsIRunnable> r
-    = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_FREE_SPACE,
-                               win, mPrincipal, dsf, request);
-  nsresult rv = NS_DispatchToCurrentThread(r);
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-  }
-  return request.forget();
-}
-
-already_AddRefed<DOMRequest>
-nsDOMDeviceStorage::UsedSpace(ErrorResult& aRv)
+nsDOMDeviceStorage::AddOrAppendNamed(Blob* aBlob, const nsAString& aPath,
+                                     bool aCreate, ErrorResult& aRv)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
-  if (!win) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  DebugOnly<DeviceStorageUsedSpaceCache*> usedSpaceCache
-    = DeviceStorageUsedSpaceCache::CreateOrGet();
-  MOZ_ASSERT(usedSpaceCache);
-
-  nsRefPtr<DOMRequest> request = new DOMRequest(win);
-
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
-                                                          mStorageName);
-  nsCOMPtr<nsIRunnable> r
-    = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_USED_SPACE,
-                               win, mPrincipal, dsf, request);
-  nsresult rv = NS_DispatchToCurrentThread(r);
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-  }
-  return request.forget();
-}
-
-already_AddRefed<DOMRequest>
-nsDOMDeviceStorage::Available(ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
-  if (!win) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  nsRefPtr<DOMRequest> request = new DOMRequest(win);
-
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
-                                                          mStorageName);
-  nsCOMPtr<nsIRunnable> r
-    = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_AVAILABLE,
-                               win, mPrincipal, dsf, request);
-  nsresult rv = NS_DispatchToCurrentThread(r);
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-  }
-  return request.forget();
-}
-
-already_AddRefed<DOMRequest>
-nsDOMDeviceStorage::StorageStatus(ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
-  if (!win) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  nsRefPtr<DOMRequest> request = new DOMRequest(win);
-
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
-                                                          mStorageName);
-  nsCOMPtr<nsIRunnable> r
-    = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_STATUS,
-                               win, mPrincipal, dsf, request);
-  nsresult rv = NS_DispatchToCurrentThread(r);
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-  }
-  return request.forget();
-}
-
-already_AddRefed<DOMRequest>
-nsDOMDeviceStorage::Format(ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
-  if (!win) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  nsRefPtr<DOMRequest> request = new DOMRequest(win);
-
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
-                                                          mStorageName);
-  nsCOMPtr<nsIRunnable> r
-    = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_FORMAT,
-                               win, mPrincipal, dsf, request);
-  nsresult rv = NS_DispatchToCurrentThread(r);
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-  }
-  return request.forget();
-}
-
-already_AddRefed<DOMRequest>
-nsDOMDeviceStorage::Mount(ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
-  if (!win) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  nsRefPtr<DOMRequest> request = new DOMRequest(win);
-
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
-                                                          mStorageName);
-  nsCOMPtr<nsIRunnable> r
-    = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_MOUNT,
-                               win, mPrincipal, dsf, request);
-  nsresult rv = NS_DispatchToCurrentThread(r);
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-  }
-  return request.forget();
-}
-
-already_AddRefed<DOMRequest>
-nsDOMDeviceStorage::Unmount(ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
-  if (!win) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  nsRefPtr<DOMRequest> request = new DOMRequest(win);
-
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
-                                                          mStorageName);
-  nsCOMPtr<nsIRunnable> r
-    = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_UNMOUNT,
-                               win, mPrincipal, dsf, request);
-  nsresult rv = NS_DispatchToCurrentThread(r);
-  if (NS_FAILED(rv)) {
-    aRv.Throw(rv);
-  }
-  return request.forget();
-}
-
-already_AddRefed<DOMRequest>
-nsDOMDeviceStorage::CreateFileDescriptor(const nsAString& aPath,
-                                         DeviceStorageFileDescriptor* aDSFileDescriptor,
-                                         ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aDSFileDescriptor);
-
-  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
-  if (!win) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
+  MOZ_ASSERT(IsOwningThread());
+
+  // if the blob is null here, bail
+  if (!aBlob) {
     return nullptr;
   }
 
   DeviceStorageTypeChecker* typeChecker
     = DeviceStorageTypeChecker::CreateOrGet();
   if (!typeChecker) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsCOMPtr<nsIRunnable> r;
 
   if (IsFullPath(aPath)) {
     nsString storagePath;
     nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
     if (!ds) {
-      nsRefPtr<DOMRequest> request = new DOMRequest(win);
-      r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
-      aRv = NS_DispatchToCurrentThread(r);
-      if (aRv.Failed()) {
-        return nullptr;
-      }
-      return request.forget();
+      return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
+    }
+
+    return ds->AddOrAppendNamed(aBlob, storagePath, aCreate, aRv);
+  }
+
+  nsRefPtr<DOMRequest> domRequest;
+  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName,
+                                                          aPath);
+  if (!dsf->IsSafePath()) {
+    aRv = mManager->Reject(id, POST_ERROR_EVENT_PERMISSION_DENIED);
+  } else if (!typeChecker->Check(mStorageType, dsf->mFile) ||
+      !typeChecker->Check(mStorageType, aBlob->Impl())) {
+    aRv = mManager->Reject(id, POST_ERROR_EVENT_ILLEGAL_TYPE);
+  } else {
+    nsRefPtr<DeviceStorageRequest> request;
+    if (aCreate) {
+      request = new DeviceStorageCreateRequest();
+    } else {
+      request = new DeviceStorageAppendRequest();
     }
-    return ds->CreateFileDescriptor(storagePath, aDSFileDescriptor, aRv);
-  }
-
-  nsRefPtr<DOMRequest> request = new DOMRequest(win);
+    request->Initialize(mManager, dsf, id, aBlob->Impl());
+    aRv = CheckPermission(request);
+  }
+
+  return domRequest.forget();
+}
+
+already_AddRefed<DOMRequest>
+nsDOMDeviceStorage::GetInternal(const nsAString& aPath, bool aEditable,
+                                ErrorResult& aRv)
+{
+  MOZ_ASSERT(IsOwningThread());
+
+  if (IsFullPath(aPath)) {
+    nsString storagePath;
+    nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
+    if (!ds) {
+      return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
+    }
+    return ds->GetInternal(storagePath, aEditable, aRv);
+  }
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName,
+                                                          aPath);
+  dsf->SetEditable(aEditable);
+  if (!dsf->IsSafePath()) {
+    return CreateAndRejectDOMRequest(POST_ERROR_EVENT_PERMISSION_DENIED, aRv);
+  }
+
+  nsRefPtr<DOMRequest> domRequest;
+  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<DeviceStorageRequest> request = new DeviceStorageOpenRequest();
+  request->Initialize(mManager, dsf, id);
+
+  aRv = CheckPermission(request);
+  return domRequest.forget();
+}
+
+already_AddRefed<DOMRequest>
+nsDOMDeviceStorage::Delete(const nsAString& aPath, ErrorResult& aRv)
+{
+  MOZ_ASSERT(IsOwningThread());
+
+  if (IsFullPath(aPath)) {
+    nsString storagePath;
+    nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
+    if (!ds) {
+      return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
+    }
+    return ds->Delete(storagePath, aRv);
+  }
 
   nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                           mStorageName,
                                                           aPath);
   if (!dsf->IsSafePath()) {
-    r = new PostErrorEvent(request, POST_ERROR_EVENT_PERMISSION_DENIED);
-  } else if (!typeChecker->Check(mStorageType, dsf->mFile)) {
-    r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_TYPE);
-  } else {
-    r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_CREATEFD,
-                                 win, mPrincipal, dsf, request,
-                                 aDSFileDescriptor);
-  }
-
-  aRv = NS_DispatchToCurrentThread(r);
+    return CreateAndRejectDOMRequest(POST_ERROR_EVENT_PERMISSION_DENIED, aRv);
+  }
+
+  nsRefPtr<DOMRequest> domRequest;
+  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<DeviceStorageRequest> request = new DeviceStorageDeleteRequest();
+  request->Initialize(mManager, dsf, id);
+
+  aRv = CheckPermission(request);
+  return domRequest.forget();
+}
+
+already_AddRefed<DOMRequest>
+nsDOMDeviceStorage::FreeSpace(ErrorResult& aRv)
+{
+  MOZ_ASSERT(IsOwningThread());
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName);
+
+  nsRefPtr<DOMRequest> domRequest;
+  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<DeviceStorageRequest> request = new DeviceStorageFreeSpaceRequest();
+  request->Initialize(mManager, dsf, id);
+
+  aRv = CheckPermission(request);
+  return domRequest.forget();
+}
+
+already_AddRefed<DOMRequest>
+nsDOMDeviceStorage::UsedSpace(ErrorResult& aRv)
+{
+  MOZ_ASSERT(IsOwningThread());
+
+  DebugOnly<DeviceStorageUsedSpaceCache*> usedSpaceCache
+    = DeviceStorageUsedSpaceCache::CreateOrGet();
+  MOZ_ASSERT(usedSpaceCache);
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName);
+
+  nsRefPtr<DOMRequest> domRequest;
+  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<DeviceStorageRequest> request = new DeviceStorageUsedSpaceRequest();
+  request->Initialize(mManager, dsf, id);
+
+  aRv = CheckPermission(request);
+  return domRequest.forget();
+}
+
+already_AddRefed<DOMRequest>
+nsDOMDeviceStorage::Available(ErrorResult& aRv)
+{
+  MOZ_ASSERT(IsOwningThread());
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName);
+
+  nsRefPtr<DOMRequest> domRequest;
+  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<DeviceStorageRequest> request = new DeviceStorageAvailableRequest();
+  request->Initialize(mManager, dsf, id);
+
+  aRv = CheckPermission(request);
+  return domRequest.forget();
+}
+
+already_AddRefed<DOMRequest>
+nsDOMDeviceStorage::StorageStatus(ErrorResult& aRv)
+{
+  MOZ_ASSERT(IsOwningThread());
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName);
+
+  nsRefPtr<DOMRequest> domRequest;
+  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
-  return request.forget();
+
+  nsRefPtr<DeviceStorageRequest> request = new DeviceStorageStatusRequest();
+  request->Initialize(mManager, dsf, id);
+
+  aRv = CheckPermission(request);
+  return domRequest.forget();
+}
+
+already_AddRefed<DOMRequest>
+nsDOMDeviceStorage::Format(ErrorResult& aRv)
+{
+  MOZ_ASSERT(IsOwningThread());
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName);
+
+  nsRefPtr<DOMRequest> domRequest;
+  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<DeviceStorageRequest> request = new DeviceStorageFormatRequest();
+  request->Initialize(mManager, dsf, id);
+
+  aRv = CheckPermission(request);
+  return domRequest.forget();
+}
+
+already_AddRefed<DOMRequest>
+nsDOMDeviceStorage::Mount(ErrorResult& aRv)
+{
+  MOZ_ASSERT(IsOwningThread());
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName);
+
+  nsRefPtr<DOMRequest> domRequest;
+  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<DeviceStorageRequest> request = new DeviceStorageMountRequest();
+  request->Initialize(mManager, dsf, id);
+
+  aRv = CheckPermission(request);
+  return domRequest.forget();
+}
+
+already_AddRefed<DOMRequest>
+nsDOMDeviceStorage::Unmount(ErrorResult& aRv)
+{
+  MOZ_ASSERT(IsOwningThread());
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName);
+
+  nsRefPtr<DOMRequest> domRequest;
+  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+
+  nsRefPtr<DeviceStorageRequest> request = new DeviceStorageUnmountRequest();
+  request->Initialize(mManager, dsf, id);
+
+  aRv = CheckPermission(request);
+  return domRequest.forget();
+}
+
+already_AddRefed<DOMRequest>
+nsDOMDeviceStorage::CreateFileDescriptor(const nsAString& aPath,
+                                         DeviceStorageFileDescriptor* aDSFileDescriptor,
+                                         ErrorResult& aRv)
+{
+  MOZ_ASSERT(IsOwningThread());
+  MOZ_ASSERT(aDSFileDescriptor);
+
+  DeviceStorageTypeChecker* typeChecker
+    = DeviceStorageTypeChecker::CreateOrGet();
+  if (!typeChecker) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  if (IsFullPath(aPath)) {
+    nsString storagePath;
+    nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
+    if (!ds) {
+      return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
+    }
+    return ds->CreateFileDescriptor(storagePath, aDSFileDescriptor, aRv);
+  }
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName,
+                                                          aPath);
+  if (!dsf->IsSafePath()) {
+    return CreateAndRejectDOMRequest(POST_ERROR_EVENT_PERMISSION_DENIED, aRv);
+  }
+
+  if (!typeChecker->Check(mStorageType, dsf->mFile)) {
+    return CreateAndRejectDOMRequest(POST_ERROR_EVENT_ILLEGAL_TYPE, aRv);
+  }
+
+  nsRefPtr<DOMRequest> domRequest;
+  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsRefPtr<DeviceStorageRequest> request = new DeviceStorageCreateFdRequest();
+  request->Initialize(mManager, dsf, id, aDSFileDescriptor);
+
+  aRv = CheckPermission(request);
+  return domRequest.forget();
 }
 
 bool
 nsDOMDeviceStorage::Default()
 {
   nsString defaultStorageName;
   GetDefaultStorageName(mStorageType, defaultStorageName);
   return mStorageName.Equals(defaultStorageName);
@@ -3830,46 +3407,42 @@ nsDOMDeviceStorage::EnumerateEditable(co
 }
 
 
 already_AddRefed<DOMCursor>
 nsDOMDeviceStorage::EnumerateInternal(const nsAString& aPath,
                                       const EnumerationParameters& aOptions,
                                       bool aEditable, ErrorResult& aRv)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
-  if (!win) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
+  MOZ_ASSERT(IsOwningThread());
 
   PRTime since = 0;
   if (aOptions.mSince.WasPassed() && !aOptions.mSince.Value().IsUndefined()) {
     since = PRTime(aOptions.mSince.Value().TimeStamp().toDouble());
   }
 
   nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                           mStorageName,
                                                           aPath,
                                                           EmptyString());
   dsf->SetEditable(aEditable);
 
-  nsRefPtr<nsDOMDeviceStorageCursor> cursor
-    = new nsDOMDeviceStorageCursor(win, mPrincipal, dsf, since);
-  nsRefPtr<DeviceStorageCursorRequest> r
-    = new DeviceStorageCursorRequest(cursor);
-
-  if (DeviceStorageStatics::IsPromptTesting()) {
-    r->Allow(JS::UndefinedHandleValue);
-    return cursor.forget();
-  }
-
-  nsContentPermissionUtils::AskPermission(r, win);
+  nsRefPtr<DeviceStorageCursorRequest> request = new DeviceStorageCursorRequest();
+  nsRefPtr<nsDOMDeviceStorageCursor> cursor;
+  uint32_t id = CreateDOMCursor(request, getter_AddRefs(cursor), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  if (!dsf->IsSafePath()) {
+    aRv = mManager->Reject(id, POST_ERROR_EVENT_PERMISSION_DENIED);
+  } else {
+    request->Initialize(mManager, dsf, id, since);
+    aRv = CheckPermission(request);
+  }
 
   return cursor.forget();
 }
 
 void
 nsDOMDeviceStorage::OnWritableNameChanged()
 {
   nsAdoptingString DefaultLocation;
@@ -3946,37 +3519,37 @@ nsDOMDeviceStorage::DispatchStorageStatu
   bool ignore;
   DispatchEvent(event, &ignore);
 }
 #endif
 
 void
 nsDOMDeviceStorage::OnFileWatcherUpdate(const nsCString& aData, DeviceStorageFile* aFile)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(IsOwningThread());
   Notify(aData.get(), aFile);
 }
 
 void
 nsDOMDeviceStorage::OnDiskSpaceWatcher(bool aLowDiskSpace)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(IsOwningThread());
   nsRefPtr<DeviceStorageFile> file =
     new DeviceStorageFile(mStorageType, mStorageName);
   if (aLowDiskSpace) {
     Notify("low-disk-space", file);
   } else {
     Notify("available-disk-space", file);
   }
 }
 
 #ifdef MOZ_WIDGET_GONK
 void
 nsDOMDeviceStorage::OnVolumeStateChanged(nsIVolume* aVolume) {
-  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(IsOwningThread());
 
   // We invalidate the used space cache for the volume that actually changed
   // state.
   nsString volName;
   aVolume->GetName(volName);
 
   DeviceStorageUsedSpaceCache* usedSpaceCache
     = DeviceStorageUsedSpaceCache::CreateOrGet();
@@ -3999,17 +3572,22 @@ nsDOMDeviceStorage::OnVolumeStateChanged
   dsf->GetStorageStatus(storageStatus);
   DispatchStorageStatusChangeEvent(storageStatus);
 }
 #endif
 
 nsresult
 nsDOMDeviceStorage::Notify(const char* aReason, DeviceStorageFile* aFile)
 {
-  if (!mAllowedToWatchFile) {
+  if (!mManager) {
+    return NS_OK;
+  }
+
+  if (mManager->CheckPermission(DEVICE_STORAGE_ACCESS_READ) !=
+      nsIPermissionManager::ALLOW_ACTION) {
     return NS_OK;
   }
 
   if (!mStorageType.Equals(aFile->mStorageType) ||
       !mStorageName.Equals(aFile->mStorageName)) {
     // Ignore this
     return NS_OK;
   }
@@ -4031,31 +3609,509 @@ nsDOMDeviceStorage::Notify(const char* a
   return NS_OK;
 }
 
 void
 nsDOMDeviceStorage::EventListenerWasAdded(const nsAString& aType,
                                           ErrorResult& aRv,
                                           JSCompartment* aCompartment)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (!aType.EqualsLiteral("change")) {
+  MOZ_ASSERT(IsOwningThread());
+
+  if (!mManager) {
+    return;
+  }
+
+  if (mManager->CheckPermission(DEVICE_STORAGE_ACCESS_READ) !=
+      nsIPermissionManager::PROMPT_ACTION) {
     return;
   }
 
-  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
-  if (NS_WARN_IF(!win)) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
+  if (!aType.EqualsLiteral(STORAGE_CHANGE_EVENT)) {
     return;
   }
 
-  nsRefPtr<DOMRequest> request = new DOMRequest(win);
+  nsRefPtr<DOMRequest> domRequest;
+  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
   nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                           mStorageName);
-  nsCOMPtr<nsIRunnable> r
-    = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_WATCH,
-                               win, mPrincipal, dsf, request, this);
-  nsresult rv = NS_DispatchToCurrentThread(r);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(rv);
+  nsRefPtr<DeviceStorageRequest> request = new DeviceStorageWatchRequest();
+  request->Initialize(mManager, dsf, id);
+  aRv = CheckPermission(request);
+}
+
+Atomic<uint32_t> DeviceStorageRequestManager::sLastRequestId(0);
+
+DeviceStorageRequestManager::DeviceStorageRequestManager()
+  : mOwningThread(NS_GetCurrentThread())
+  , mMutex("DeviceStorageRequestManager::mMutex")
+  , mShutdown(false)
+{
+  DS_LOG_INFO("%p", this);
+  for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mPermissionCache); ++i) {
+    mPermissionCache[i] = nsIPermissionManager::PROMPT_ACTION;
+  }
+}
+
+DeviceStorageRequestManager::~DeviceStorageRequestManager()
+{
+  DS_LOG_INFO("%p pending %zu", this, mPending.Length());
+
+  if (!mPending.IsEmpty()) {
+    MOZ_ASSERT_UNREACHABLE("Should not destroy, still has pending requests");
+    ListIndex i = mPending.Length();
+    while (i > 0) {
+      --i;
+      DS_LOG_ERROR("terminate %u", mPending[i].mId);
+      NS_ProxyRelease(mOwningThread,
+        NS_ISUPPORTS_CAST(EventTarget*, mPending[i].mRequest.forget().take()));
+    }
   }
 }
+
+void
+DeviceStorageRequestManager::StorePermission(size_t aAccess, bool aAllow)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aAccess < MOZ_ARRAY_LENGTH(mPermissionCache));
+
+  MutexAutoLock lock(mMutex);
+  mPermissionCache[aAccess] = aAllow ? nsIPermissionManager::ALLOW_ACTION
+                                     : nsIPermissionManager::DENY_ACTION;
+  DS_LOG_INFO("access %zu cache %u", aAccess, mPermissionCache[aAccess]);
+}
+
+uint32_t
+DeviceStorageRequestManager::CheckPermission(size_t aAccess)
+{
+  MOZ_ASSERT(IsOwningThread() || NS_IsMainThread());
+  MOZ_ASSERT(aAccess < MOZ_ARRAY_LENGTH(mPermissionCache));
+
+  MutexAutoLock lock(mMutex);
+  DS_LOG_INFO("access %zu cache %u", aAccess, mPermissionCache[aAccess]);
+  return mPermissionCache[aAccess];
+}
+
+bool
+DeviceStorageRequestManager::IsOwningThread()
+{
+  bool owner = false;
+  mOwningThread->IsOnCurrentThread(&owner);
+  return owner;
+}
+
+nsresult
+DeviceStorageRequestManager::DispatchToOwningThread(nsIRunnable* aRunnable)
+{
+  return mOwningThread->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
+}
+
+nsresult
+DeviceStorageRequestManager::DispatchOrAbandon(uint32_t aId,
+                                               nsIRunnable* aRunnable)
+{
+  MutexAutoLock lock(mMutex);
+  if (mShutdown) {
+    /* Dispatching in this situation may result in the runnable being
+       silently discarded but not freed. The runnables themselves are
+       safe to be freed off the owner thread but the dispatch method
+       does not know that. */
+    DS_LOG_DEBUG("shutdown %u", aId);
+    return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+  }
+
+  nsresult rv = DispatchToOwningThread(aRunnable);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    DS_LOG_ERROR("abandon %u", aId);
+  }
+  return rv;
+}
+
+uint32_t
+DeviceStorageRequestManager::Create(nsDOMDeviceStorage* aDeviceStorage,
+                                    DeviceStorageCursorRequest* aRequest,
+                                    nsDOMDeviceStorageCursor** aCursor)
+{
+  MOZ_ASSERT(aDeviceStorage);
+  MOZ_ASSERT(aRequest);
+  MOZ_ASSERT(aCursor);
+
+  nsRefPtr<nsDOMDeviceStorageCursor> request
+    = new nsDOMDeviceStorageCursor(aDeviceStorage->GetOwnerGlobal(), aRequest);
+  uint32_t id = CreateInternal(request, true);
+  DS_LOG_INFO("%u", id);
+  request.forget(aCursor);
+  return id;
+}
+
+uint32_t
+DeviceStorageRequestManager::Create(nsDOMDeviceStorage* aDeviceStorage,
+                                    DOMRequest** aRequest)
+{
+  MOZ_ASSERT(aDeviceStorage);
+  MOZ_ASSERT(aRequest);
+
+  nsRefPtr<DOMRequest> request
+    = new DOMRequest(aDeviceStorage->GetOwnerGlobal());
+  uint32_t id = CreateInternal(request, false);
+  DS_LOG_INFO("%u", id);
+  request.forget(aRequest);
+  return id;
+}
+
+uint32_t
+DeviceStorageRequestManager::CreateInternal(DOMRequest* aRequest, bool aCursor)
+{
+  MOZ_ASSERT(IsOwningThread());
+  MOZ_ASSERT(!mShutdown);
+
+  uint32_t id;
+  do {
+    id = ++sLastRequestId;
+  } while (id == INVALID_ID || Find(id) != mPending.Length());
+
+  ListEntry* entry = mPending.AppendElement();
+  entry->mId = id;
+  entry->mRequest = aRequest;
+  entry->mCursor = aCursor;
+  return entry->mId;
+}
+
+nsresult
+DeviceStorageRequestManager::Resolve(uint32_t aId, bool aForceDispatch)
+{
+  if (aForceDispatch || !IsOwningThread()) {
+    DS_LOG_DEBUG("recv %u", aId);
+    nsRefPtr<DeviceStorageRequestManager> self = this;
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self, aId] () -> void
+    {
+      self->Resolve(aId, false);
+    });
+    return DispatchOrAbandon(aId, r);
+  }
+
+  DS_LOG_INFO("posted %u", aId);
+
+  if (NS_WARN_IF(aId == INVALID_ID)) {
+    DS_LOG_ERROR("invalid");
+    MOZ_ASSERT_UNREACHABLE("resolve invalid request");
+    return NS_OK;
+  }
+
+  ListIndex i = Find(aId);
+  if (NS_WARN_IF(i == mPending.Length())) {
+    return NS_OK;
+  }
+
+  return ResolveInternal(i, JS::UndefinedHandleValue);
+}
+
+nsresult
+DeviceStorageRequestManager::Resolve(uint32_t aId, const nsString& aResult,
+                                     bool aForceDispatch)
+{
+  if (aForceDispatch || !IsOwningThread()) {
+    DS_LOG_DEBUG("recv %u", aId);
+    nsRefPtr<DeviceStorageRequestManager> self = this;
+    nsString result = aResult;
+    nsCOMPtr<nsIRunnable> r
+      = NS_NewRunnableFunction([self, aId, result] () -> void
+    {
+      self->Resolve(aId, result, false);
+    });
+    return DispatchOrAbandon(aId, r);
+  }
+
+  DS_LOG_INFO("posted %u w/ %s", aId,
+              NS_LossyConvertUTF16toASCII(aResult).get());
+
+  if (NS_WARN_IF(aId == INVALID_ID)) {
+    DS_LOG_WARN("invalid");
+    MOZ_ASSERT_UNREACHABLE("resolve invalid request");
+    return NS_OK;
+  }
+
+  ListIndex i = Find(aId);
+  if (NS_WARN_IF(i == mPending.Length())) {
+    return NS_OK;
+  }
+
+  nsIGlobalObject* global = mPending[i].mRequest->GetOwnerGlobal();
+
+  AutoJSAPI jsapi;
+  if (NS_WARN_IF(!jsapi.Init(global))) {
+    return RejectInternal(i, NS_LITERAL_STRING(POST_ERROR_EVENT_UNKNOWN));
+  }
+
+  JS::RootedValue rvalue(jsapi.cx());
+  JS::MutableHandleValue mvalue(&rvalue);
+  if (NS_WARN_IF(!xpc::StringToJsval(jsapi.cx(), aResult, mvalue))) {
+    return RejectInternal(i, NS_LITERAL_STRING(POST_ERROR_EVENT_UNKNOWN));
+  }
+
+  return ResolveInternal(i, rvalue);
+}
+
+nsresult
+DeviceStorageRequestManager::Resolve(uint32_t aId, uint64_t aValue,
+                                     bool aForceDispatch)
+{
+  if (aForceDispatch || !IsOwningThread()) {
+    DS_LOG_DEBUG("recv %u w/ %" PRIu64, aId, aValue);
+    nsRefPtr<DeviceStorageRequestManager> self = this;
+    nsCOMPtr<nsIRunnable> r
+      = NS_NewRunnableFunction([self, aId, aValue] () -> void
+    {
+      self->Resolve(aId, aValue, false);
+    });
+    return DispatchOrAbandon(aId, r);
+  }
+
+  DS_LOG_INFO("posted %u w/ %" PRIu64, aId, aValue);
+
+  if (NS_WARN_IF(aId == INVALID_ID)) {
+    DS_LOG_ERROR("invalid");
+    MOZ_ASSERT_UNREACHABLE("resolve invalid request");
+    return NS_OK;
+  }
+
+  ListIndex i = Find(aId);
+  if (NS_WARN_IF(i == mPending.Length())) {
+    return NS_OK;
+  }
+
+  nsIGlobalObject* global = mPending[i].mRequest->GetOwnerGlobal();
+
+  AutoJSAPI jsapi;
+  if (NS_WARN_IF(!jsapi.Init(global))) {
+    return RejectInternal(i, NS_LITERAL_STRING(POST_ERROR_EVENT_UNKNOWN));
+  }
+
+  JS::RootedValue value(jsapi.cx(), JS_NumberValue((double)aValue));
+  return ResolveInternal(i, value);
+}
+
+nsresult
+DeviceStorageRequestManager::Resolve(uint32_t aId, DeviceStorageFile* aFile,
+                                     bool aForceDispatch)
+{
+  MOZ_ASSERT(aFile);
+  DS_LOG_DEBUG("recv %u w/ %p", aId, aFile);
+
+  nsString fullPath;
+  aFile->GetFullPath(fullPath);
+
+  /* This check is useful to know if somewhere the DeviceStorageFile
+     has not been properly set. Mimetype is not checked because it can be
+     empty. */
+  MOZ_ASSERT(aFile->mLength != UINT64_MAX);
+  MOZ_ASSERT(aFile->mLastModifiedDate != UINT64_MAX);
+
+  nsRefPtr<BlobImpl> blobImpl = new BlobImplFile(fullPath, aFile->mMimeType,
+                                                 aFile->mLength, aFile->mFile,
+                                                 aFile->mLastModifiedDate);
+
+  /* File should start out as mutable by default but we should turn
+     that off if it wasn't requested. */
+  bool editable;
+  nsresult rv = blobImpl->GetMutable(&editable);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    DS_LOG_WARN("%u cannot query mutable", aId);
+    return Reject(aId, POST_ERROR_EVENT_UNKNOWN);
+  }
+
+  if (editable != aFile->mEditable) {
+    rv = blobImpl->SetMutable(aFile->mEditable);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      DS_LOG_WARN("%u cannot set mutable %d", aId, aFile->mEditable);
+      return Reject(aId, POST_ERROR_EVENT_UNKNOWN);
+    }
+  }
+
+  return Resolve(aId, blobImpl, aForceDispatch);
+}
+
+nsresult
+DeviceStorageRequestManager::Resolve(uint32_t aId, BlobImpl* aBlobImpl,
+                                     bool aForceDispatch)
+{
+  if (aForceDispatch || !IsOwningThread()) {
+    DS_LOG_DEBUG("recv %u w/ %p", aId, aBlobImpl);
+    nsRefPtr<DeviceStorageRequestManager> self = this;
+    nsRefPtr<BlobImpl> blobImpl = aBlobImpl;
+    nsCOMPtr<nsIRunnable> r
+      = NS_NewRunnableFunction([self, aId, blobImpl] () -> void
+    {
+      self->Resolve(aId, blobImpl, false);
+    });
+    return DispatchOrAbandon(aId, r);
+  }
+
+  DS_LOG_INFO("posted %u w/ %p", aId, aBlobImpl);
+
+  if (NS_WARN_IF(aId == INVALID_ID)) {
+    DS_LOG_ERROR("invalid");
+    MOZ_ASSERT_UNREACHABLE("resolve invalid request");
+    return NS_OK;
+  }
+
+  ListIndex i = Find(aId);
+  if (NS_WARN_IF(i == mPending.Length())) {
+    return NS_OK;
+  }
+
+  if (!aBlobImpl) {
+    return ResolveInternal(i, JS::NullHandleValue);
+  }
+
+  nsIGlobalObject* global = mPending[i].mRequest->GetOwnerGlobal();
+
+  AutoJSAPI jsapi;
+  if (NS_WARN_IF(!jsapi.Init(global))) {
+    return RejectInternal(i, NS_LITERAL_STRING(POST_ERROR_EVENT_UNKNOWN));
+  }
+
+  nsRefPtr<Blob> blob = Blob::Create(global, aBlobImpl);
+  JS::Rooted<JSObject*> obj(jsapi.cx(),
+                            blob->WrapObject(jsapi.cx(), nullptr));
+  MOZ_ASSERT(obj);
+  JS::RootedValue value(jsapi.cx(), JS::ObjectValue(*obj));
+  return ResolveInternal(i, value);
+}
+
+nsresult
+DeviceStorageRequestManager::ResolveInternal(ListIndex aIndex,
+                                             JS::HandleValue aResult)
+{
+  MOZ_ASSERT(IsOwningThread());
+  MOZ_ASSERT(!mShutdown);
+
+  /* Note that we must seize the DOMRequest reference and destroy the entry
+     before calling FireError because it may go straight to the JS code
+     which in term may call back into our code (as observed in Shutdown).
+     The safest thing to do is to ensure the very last thing we do is the
+     DOM call so that there is no inconsistent state. */
+  nsRefPtr<DOMRequest> request;
+  bool isCursor = mPending[aIndex].mCursor;
+  if (!isCursor || aResult.isUndefined()) {
+    request = mPending[aIndex].mRequest.forget();
+    mPending.RemoveElementAt(aIndex);
+  } else {
+    request = mPending[aIndex].mRequest;
+  }
+
+  if (isCursor) {
+    auto cursor = static_cast<nsDOMDeviceStorageCursor*>(request.get());
+
+    /* Must call it with the right pointer type since the base class does
+       not define FireDone and FireSuccess as virtual. */
+    if (aResult.isUndefined()) {
+      DS_LOG_INFO("cursor complete");
+      cursor->FireDone();
+    } else {
+      DS_LOG_INFO("cursor continue");
+      cursor->FireSuccess(aResult);
+    }
+  } else {
+    DS_LOG_INFO("request complete");
+    request->FireSuccess(aResult);
+  }
+  return NS_OK;
+}
+
+nsresult
+DeviceStorageRequestManager::RejectInternal(DeviceStorageRequestManager::ListIndex aIndex,
+                                            const nsString& aReason)
+{
+  MOZ_ASSERT(IsOwningThread());
+  MOZ_ASSERT(!mShutdown);
+
+  /* Note that we must seize the DOMRequest reference and destroy the entry
+     before calling FireError because it may go straight to the JS code
+     which in term may call back into our code (as observed in Shutdown).
+     The safest thing to do is to ensure the very last thing we do is the
+     DOM call so that there is no inconsistent state. */
+  nsRefPtr<DOMRequest> request = mPending[aIndex].mRequest.forget();
+  bool isCursor = mPending[aIndex].mCursor;
+  mPending.RemoveElementAt(aIndex);
+
+  if (isCursor) {
+    /* Must call it with the right pointer type since the base class does
+       not define FireError as virtual. */
+    auto cursor = static_cast<nsDOMDeviceStorageCursor*>(request.get());
+    cursor->FireError(aReason);
+  } else {
+    request->FireError(aReason);
+  }
+  return NS_OK;
+}
+
+nsresult
+DeviceStorageRequestManager::Reject(uint32_t aId, const nsString& aReason)
+{
+  DS_LOG_DEBUG("recv %u", aId);
+
+  if (NS_WARN_IF(aId == INVALID_ID)) {
+    DS_LOG_ERROR("invalid");
+    MOZ_ASSERT_UNREACHABLE("reject invalid request");
+    return NS_OK;
+  }
+
+  nsRefPtr<DeviceStorageRequestManager> self = this;
+  nsString reason = aReason;
+  nsCOMPtr<nsIRunnable> r
+    = NS_NewRunnableFunction([self, aId, reason] () -> void
+  {
+    DS_LOG_INFO("posted %u w/ %s", aId,
+                NS_LossyConvertUTF16toASCII(reason).get());
+
+    ListIndex i = self->Find(aId);
+    if (NS_WARN_IF(i == self->mPending.Length())) {
+      return;
+    }
+
+    self->RejectInternal(i, reason);
+  });
+  return DispatchOrAbandon(aId, r);
+}
+
+nsresult
+DeviceStorageRequestManager::Reject(uint32_t aId, const char* aReason)
+{
+  nsString reason;
+  reason.AssignASCII(aReason);
+  return Reject(aId, reason);
+}
+
+void
+DeviceStorageRequestManager::Shutdown()
+{
+  MOZ_ASSERT(IsOwningThread());
+
+  MutexAutoLock lock(mMutex);
+  mShutdown = true;
+  ListIndex i = mPending.Length();
+  DS_LOG_INFO("pending %zu", i);
+  while (i > 0) {
+    --i;
+    DS_LOG_INFO("terminate %u (%u)", mPending[i].mId, mPending[i].mCursor);
+  }
+  mPending.Clear();
+}
+
+DeviceStorageRequestManager::ListIndex
+DeviceStorageRequestManager::Find(uint32_t aId)
+{
+  MOZ_ASSERT(IsOwningThread());
+  ListIndex i = mPending.Length();
+  while (i > 0) {
+    --i;
+    if (mPending[i].mId == aId) {
+      return i;
+    }
+  }
+  DS_LOG_DEBUG("no id %u", aId);
+  return mPending.Length();
+}
--- a/dom/devicestorage/nsDeviceStorage.h
+++ b/dom/devicestorage/nsDeviceStorage.h
@@ -3,27 +3,27 @@
 /* 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 nsDeviceStorage_h
 #define nsDeviceStorage_h
 
 class nsPIDOMWindow;
+#include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Logging.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestChild.h"
 
 #include "DOMRequest.h"
 #include "DOMCursor.h"
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDOMClassInfoID.h"
 #include "nsIClassInfo.h"
-#include "nsIContentPermissionPrompt.h"
 #include "nsIDOMWindow.h"
 #include "nsIURI.h"
 #include "nsIPrincipal.h"
 #include "nsString.h"
 #include "nsWeakPtr.h"
 #include "nsIDOMEventListener.h"
 #include "nsIObserver.h"
 #include "nsIStringBundle.h"
@@ -31,23 +31,28 @@ class nsPIDOMWindow;
 #include "prtime.h"
 #include "DeviceStorage.h"
 #include "mozilla/StaticPtr.h"
 
 namespace mozilla {
 class ErrorResult;
 
 namespace dom {
-class Blob;
+class BlobImpl;
+class DeviceStorageParams;
 } // namespace dom
 } // namespace mozilla
 
+class nsDOMDeviceStorage;
+class DeviceStorageCursorRequest;
+
 //#define DS_LOGGING 1
 
 #ifdef DS_LOGGING
+// FIXME -- use MOZ_LOG and set to warn by default
 #define DS_LOG_DEBUG(msg, ...)  printf_stderr("[%s:%d] " msg "\n", __func__, __LINE__, ##__VA_ARGS__)
 #define DS_LOG_INFO DS_LOG_DEBUG
 #define DS_LOG_WARN DS_LOG_DEBUG
 #define DS_LOG_ERROR DS_LOG_DEBUG
 #else
 #define DS_LOG_DEBUG(msg, ...)
 #define DS_LOG_INFO(msg, ...)
 #define DS_LOG_WARN(msg, ...)
@@ -57,30 +62,39 @@ class Blob;
 #define POST_ERROR_EVENT_FILE_EXISTS                 "NoModificationAllowedError"
 #define POST_ERROR_EVENT_FILE_DOES_NOT_EXIST         "NotFoundError"
 #define POST_ERROR_EVENT_FILE_NOT_ENUMERABLE         "TypeMismatchError"
 #define POST_ERROR_EVENT_PERMISSION_DENIED           "SecurityError"
 #define POST_ERROR_EVENT_ILLEGAL_TYPE                "TypeMismatchError"
 #define POST_ERROR_EVENT_UNKNOWN                     "Unknown"
 
 enum DeviceStorageRequestType {
-    DEVICE_STORAGE_REQUEST_READ,
-    DEVICE_STORAGE_REQUEST_WRITE,
-    DEVICE_STORAGE_REQUEST_APPEND,
-    DEVICE_STORAGE_REQUEST_CREATE,
-    DEVICE_STORAGE_REQUEST_DELETE,
-    DEVICE_STORAGE_REQUEST_WATCH,
-    DEVICE_STORAGE_REQUEST_FREE_SPACE,
-    DEVICE_STORAGE_REQUEST_USED_SPACE,
-    DEVICE_STORAGE_REQUEST_AVAILABLE,
-    DEVICE_STORAGE_REQUEST_STATUS,
-    DEVICE_STORAGE_REQUEST_FORMAT,
-    DEVICE_STORAGE_REQUEST_MOUNT,
-    DEVICE_STORAGE_REQUEST_UNMOUNT,
-    DEVICE_STORAGE_REQUEST_CREATEFD
+  DEVICE_STORAGE_REQUEST_READ,
+  DEVICE_STORAGE_REQUEST_WRITE,
+  DEVICE_STORAGE_REQUEST_APPEND,
+  DEVICE_STORAGE_REQUEST_CREATE,
+  DEVICE_STORAGE_REQUEST_DELETE,
+  DEVICE_STORAGE_REQUEST_WATCH,
+  DEVICE_STORAGE_REQUEST_FREE_SPACE,
+  DEVICE_STORAGE_REQUEST_USED_SPACE,
+  DEVICE_STORAGE_REQUEST_AVAILABLE,
+  DEVICE_STORAGE_REQUEST_STATUS,
+  DEVICE_STORAGE_REQUEST_FORMAT,
+  DEVICE_STORAGE_REQUEST_MOUNT,
+  DEVICE_STORAGE_REQUEST_UNMOUNT,
+  DEVICE_STORAGE_REQUEST_CREATEFD,
+  DEVICE_STORAGE_REQUEST_CURSOR
+};
+
+enum DeviceStorageAccessType {
+  DEVICE_STORAGE_ACCESS_READ,
+  DEVICE_STORAGE_ACCESS_WRITE,
+  DEVICE_STORAGE_ACCESS_CREATE,
+  DEVICE_STORAGE_ACCESS_UNDEFINED,
+  DEVICE_STORAGE_ACCESS_COUNT
 };
 
 class DeviceStorageUsedSpaceCache final
 {
 public:
   static DeviceStorageUsedSpaceCache* CreateOrGet();
 
   DeviceStorageUsedSpaceCache();
@@ -171,89 +185,251 @@ class DeviceStorageTypeChecker final
 public:
   static DeviceStorageTypeChecker* CreateOrGet();
 
   DeviceStorageTypeChecker();
   ~DeviceStorageTypeChecker();
 
   void InitFromBundle(nsIStringBundle* aBundle);
 
-  bool Check(const nsAString& aType, mozilla::dom::Blob* aBlob);
+  bool Check(const nsAString& aType, mozilla::dom::BlobImpl* aBlob);
   bool Check(const nsAString& aType, nsIFile* aFile);
   bool Check(const nsAString& aType, const nsString& aPath);
   void GetTypeFromFile(nsIFile* aFile, nsAString& aType);
   void GetTypeFromFileName(const nsAString& aFileName, nsAString& aType);
-
-  static nsresult GetPermissionForType(const nsAString& aType, nsACString& aPermissionResult);
-  static nsresult GetAccessForRequest(const DeviceStorageRequestType aRequestType, nsACString& aAccessResult);
+  static nsresult GetPermissionForType(const nsAString& aType,
+                                       nsACString& aPermissionResult);
+  static nsresult GetAccessForRequest(const DeviceStorageRequestType aRequestType,
+                                      nsACString& aAccessResult);
+  static nsresult GetAccessForIndex(size_t aAccessIndex, nsACString& aAccessResult);
+  static size_t GetAccessIndexForRequest(const DeviceStorageRequestType aRequestType);
   static bool IsVolumeBased(const nsAString& aType);
   static bool IsSharedMediaRoot(const nsAString& aType);
 
 private:
   nsString mPicturesExtensions;
   nsString mVideosExtensions;
   nsString mMusicExtensions;
 
   static mozilla::StaticAutoPtr<DeviceStorageTypeChecker> sDeviceStorageTypeChecker;
 };
 
-class ContinueCursorEvent final : public nsRunnable
+class nsDOMDeviceStorageCursor final
+  : public mozilla::dom::DOMCursor
 {
 public:
-  explicit ContinueCursorEvent(already_AddRefed<mozilla::dom::DOMRequest> aRequest);
-  explicit ContinueCursorEvent(mozilla::dom::DOMRequest* aRequest);
-  ~ContinueCursorEvent();
-  void Continue();
-
-  NS_IMETHOD Run() override;
-private:
-  already_AddRefed<DeviceStorageFile> GetNextFile();
-  nsRefPtr<mozilla::dom::DOMRequest> mRequest;
-};
-
-class nsDOMDeviceStorageCursor final
-  : public mozilla::dom::DOMCursor
-  , public nsIContentPermissionRequest
-  , public mozilla::dom::devicestorage::DeviceStorageRequestChildCallback
-{
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_NSICONTENTPERMISSIONREQUEST
   NS_FORWARD_NSIDOMDOMCURSOR(mozilla::dom::DOMCursor::)
 
   // DOMCursor
   virtual void Continue(mozilla::ErrorResult& aRv) override;
 
-  nsDOMDeviceStorageCursor(nsPIDOMWindow* aWindow,
-                           nsIPrincipal* aPrincipal,
-                           DeviceStorageFile* aFile,
-                           PRTime aSince);
+  nsDOMDeviceStorageCursor(nsIGlobalObject* aGlobal,
+                           DeviceStorageCursorRequest* aRequest);
+
+  void FireSuccess(JS::Handle<JS::Value> aResult);
+  void FireError(const nsString& aReason);
+  void FireDone();
+
+private:
+  virtual ~nsDOMDeviceStorageCursor();
 
+  bool mOkToCallContinue;
+  nsRefPtr<DeviceStorageCursorRequest> mRequest;
+};
+
+class DeviceStorageRequestManager final
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DeviceStorageRequestManager)
+
+  static const uint32_t INVALID_ID = 0;
+
+  DeviceStorageRequestManager();
+
+  bool IsOwningThread();
+  nsresult DispatchToOwningThread(nsIRunnable* aRunnable);
 
-  nsTArray<nsRefPtr<DeviceStorageFile> > mFiles;
-  bool mOkToCallContinue;
-  PRTime mSince;
-  size_t mIndex;
+  void StorePermission(size_t aAccess, bool aAllow);
+  uint32_t CheckPermission(size_t aAccess);
+
+  /* These must be called on the owning thread context of the device
+     storage object. It will hold onto a device storage reference until
+     all of the pending requests are completed or shutdown is called. */
+  uint32_t Create(nsDOMDeviceStorage* aDeviceStorage,
+                  mozilla::dom::DOMRequest** aRequest);
+  uint32_t Create(nsDOMDeviceStorage* aDeviceStorage,
+                  DeviceStorageCursorRequest* aRequest,
+                  nsDOMDeviceStorageCursor** aCursor);
 
-  void GetStorageType(nsAString & aType);
+  /* These may be called from any thread context and post a request
+     to the owning thread to resolve the underlying DOMRequest or
+     DOMCursor. In order to trigger FireDone for a DOMCursor, one
+     should call Resolve with only the request ID. */
+  nsresult Resolve(uint32_t aId, bool aForceDispatch);
+  nsresult Resolve(uint32_t aId, const nsString& aValue, bool aForceDispatch);
+  nsresult Resolve(uint32_t aId, uint64_t aValue, bool aForceDispatch);
+  nsresult Resolve(uint32_t aId, DeviceStorageFile* aValue, bool aForceDispatch);
+  nsresult Resolve(uint32_t aId, mozilla::dom::BlobImpl* aValue, bool aForceDispatch);
+  nsresult Reject(uint32_t aId, const nsString& aReason);
+  nsresult Reject(uint32_t aId, const char* aReason);
 
-  void RequestComplete() override;
+  void Shutdown();
 
 private:
-  ~nsDOMDeviceStorageCursor();
+  DeviceStorageRequestManager(const DeviceStorageRequestManager&) = delete;
+  DeviceStorageRequestManager& operator=(const DeviceStorageRequestManager&) = delete;
+
+  struct ListEntry {
+    nsRefPtr<mozilla::dom::DOMRequest> mRequest;
+    uint32_t mId;
+    bool mCursor;
+  };
+
+  typedef nsTArray<ListEntry> ListType;
+  typedef ListType::index_type ListIndex;
 
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsCOMPtr<nsIPrincipal> mPrincipal;
-  nsCOMPtr<nsIContentPermissionRequester> mRequester;
+  virtual ~DeviceStorageRequestManager();
+  uint32_t CreateInternal(mozilla::dom::DOMRequest* aRequest, bool aCursor);
+  nsresult ResolveInternal(ListIndex aIndex, JS::HandleValue aResult);
+  nsresult RejectInternal(ListIndex aIndex, const nsString& aReason);
+  nsresult DispatchOrAbandon(uint32_t aId, nsIRunnable* aRunnable);
+  ListType::index_type Find(uint32_t aId);
+
+  nsCOMPtr<nsIThread> mOwningThread;
+  ListType mPending; // owning thread or destructor only
+
+  mozilla::Mutex mMutex;
+  uint32_t mPermissionCache[DEVICE_STORAGE_ACCESS_COUNT];
+  bool mShutdown;
+
+  static mozilla::Atomic<uint32_t> sLastRequestId;
 };
 
-//helpers
-bool
-StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString,
-              JS::MutableHandle<JS::Value> result);
+class DeviceStorageRequest
+  : public nsRunnable
+{
+protected:
+  DeviceStorageRequest();
+
+public:
+  virtual void Initialize(DeviceStorageRequestManager* aManager,
+                          DeviceStorageFile* aFile,
+                          uint32_t aRequest);
+
+  virtual void Initialize(DeviceStorageRequestManager* aManager,
+                          DeviceStorageFile* aFile,
+                          uint32_t aRequest,
+                          mozilla::dom::BlobImpl* aBlob);
+
+  virtual void Initialize(DeviceStorageRequestManager* aManager,
+                          DeviceStorageFile* aFile,
+                          uint32_t aRequest,
+                          DeviceStorageFileDescriptor* aDSFileDescriptor);
+
+  DeviceStorageAccessType GetAccess() const;
+  void GetStorageType(nsAString& aType) const;
+  DeviceStorageFile* GetFile() const;
+  DeviceStorageFileDescriptor* GetFileDescriptor() const;
+  DeviceStorageRequestManager* GetManager() const;
+
+  uint32_t GetId() const
+  {
+    return mId;
+  }
+
+  void PermissionCacheMissed()
+  {
+    mPermissionCached = false;
+  }
+
+  nsresult Cancel();
+  nsresult Allow();
+
+  nsresult Resolve()
+  {
+    /* Always dispatch an empty resolve because that signals a cursor end
+       and should not be executed directly from the caller's context due
+       to the object potentially getting freed before we return. */
+    uint32_t id = mId;
+    mId = DeviceStorageRequestManager::INVALID_ID;
+    return mManager->Resolve(id, true);
+  }
+
+  template<class T>
+  nsresult Resolve(T aValue)
+  {
+    uint32_t id = mId;
+    if (!mMultipleResolve) {
+      mId = DeviceStorageRequestManager::INVALID_ID;
+    }
+    return mManager->Resolve(id, aValue, ForceDispatch());
+  }
 
-JS::Value
-nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile);
+  template<class T>
+  nsresult Reject(T aReason)
+  {
+    uint32_t id = mId;
+    mId = DeviceStorageRequestManager::INVALID_ID;
+    return mManager->Reject(id, aReason);
+  }
+
+protected:
+  bool ForceDispatch() const
+  {
+    return !mSendToParent && mPermissionCached;
+  }
+
+  virtual ~DeviceStorageRequest();
+  virtual void Prepare();
+  virtual nsresult CreateSendParams(mozilla::dom::DeviceStorageParams& aParams);
+  nsresult AllowInternal();
+  nsresult SendToParentProcess();
+
+  nsRefPtr<DeviceStorageRequestManager> mManager;
+  nsRefPtr<DeviceStorageFile> mFile;
+  uint32_t mId;
+  nsRefPtr<mozilla::dom::BlobImpl> mBlob;
+  nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
+  DeviceStorageAccessType mAccess;
+  bool mSendToParent;
+  bool mUseStreamTransport;
+  bool mCheckFile;
+  bool mCheckBlob;
+  bool mMultipleResolve;
+  bool mPermissionCached;
 
-JS::Value
-InterfaceToJsval(nsPIDOMWindow* aWindow, nsISupports* aObject, const nsIID* aIID);
+private:
+  DeviceStorageRequest(const DeviceStorageRequest&) = delete;
+  DeviceStorageRequest& operator=(const DeviceStorageRequest&) = delete;
+};
+
+class DeviceStorageCursorRequest final
+  : public DeviceStorageRequest
+{
+public:
+  DeviceStorageCursorRequest();
+
+  using DeviceStorageRequest::Initialize;
+
+  virtual void Initialize(DeviceStorageRequestManager* aManager,
+                          DeviceStorageFile* aFile,
+                          uint32_t aRequest,
+                          PRTime aSince);
+
+  void AddFiles(size_t aSize);
+  void AddFile(already_AddRefed<DeviceStorageFile> aFile);
+  nsresult Continue();
+  NS_IMETHOD Run() override;
+
+protected:
+  virtual ~DeviceStorageCursorRequest()
+  { };
+
+  nsresult SendContinueToParentProcess();
+  nsresult CreateSendParams(mozilla::dom::DeviceStorageParams& aParams) override;
+
+  size_t mIndex;
+  PRTime mSince;
+  nsString mStorageType;
+  nsTArray<nsRefPtr<DeviceStorageFile> > mFiles;
+};
 
 #endif