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 289260 36d8ea398cc4ac00c99b0b4075de4f0f6bbb80f6
parent 289259 dbaf00db2ea83320d5c53e9a9f77b0e695f49d2c
child 289261 d413034042d3ba53885fdc7126a4d641badfe2e9
push id4956
push usergszorc@mozilla.com
push dateMon, 31 Aug 2015 21:56:15 +0000
reviewersdhylands
bugs1171170
milestone43.0a1
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