dom/devicestorage/nsDeviceStorage.cpp
author Botond Ballo <botond@mozilla.com>
Mon, 25 Apr 2016 14:28:46 -0400
changeset 341251 7f15db5b8c9eea2bac2c57b7c6d938b91753357a
parent 335850 fcc0936b576daa150697671849a191009ca33811
child 343599 232a786cf87fb0f68f9bda6e9316515524d2ac8a
permissions -rw-r--r--
Bug 1267351 - Add lextab.py (generated during a reftest run) to .hgignore and .gitignore. r=glandium DONTBUILD MozReview-Commit-ID: cuTju8o92D

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsDeviceStorage.h"

#include "mozilla/Attributes.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/DeviceStorageBinding.h"
#include "mozilla/dom/DeviceStorageChangeEvent.h"
#include "mozilla/dom/DeviceStorageFileSystem.h"
#include "mozilla/dom/devicestorage/PDeviceStorageRequestChild.h"
#include "mozilla/dom/Directory.h"
#include "mozilla/dom/FileSystemUtils.h"
#include "mozilla/dom/ipc/BlobChild.h"
#include "mozilla/dom/PBrowserChild.h"
#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 "nsCharSeparatedTokenizer.h"
#include "nsGlobalWindow.h"
#include "nsServiceManagerUtils.h"
#include "nsIFile.h"
#include "nsIDirectoryEnumerator.h"
#include "nsNetUtil.h"
#include "nsIOutputStream.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIPrincipal.h"
#include "nsJSUtils.h"
#include "nsContentUtils.h"
#include "nsXULAppAPI.h"
#include "DeviceStorageFileDescriptor.h"
#include "DeviceStorageRequestChild.h"
#include "DeviceStorageStatics.h"
#include "nsCRT.h"
#include "nsIObserverService.h"
#include "nsIMIMEService.h"
#include "nsCExternalHandlerService.h"
#include "nsIPermissionManager.h"
#include "nsIStringBundle.h"
#include "nsISupportsPrimitives.h"
#include "nsIDocument.h"
#include <algorithm>
#include "private/pprio.h"
#include "nsContentPermissionHelper.h"

#include "mozilla/dom/DeviceStorageBinding.h"

// Microsoft's API Name hackery sucks
#undef CreateEvent

#ifdef MOZ_WIDGET_GONK
#include "nsIVolume.h"
#include "nsIVolumeService.h"
#endif

#define DEVICESTORAGE_PROPERTIES \
  "chrome://global/content/devicestorage.properties"
#define DEFAULT_THREAD_TIMEOUT_MS 30000
#define STORAGE_CHANGE_EVENT "change"

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::dom::devicestorage;
using namespace mozilla::ipc;

namespace mozilla
{
  MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close);
} // namespace mozilla

namespace {

void
NormalizeFilePath(nsAString& aPath)
{
#if defined(XP_WIN)
  char16_t* cur = aPath.BeginWriting();
  char16_t* end = aPath.EndWriting();
  for (; cur < end; ++cur) {
    if (char16_t('\\') == *cur) {
      *cur = FILESYSTEM_DOM_PATH_SEPARATOR_CHAR;
    }
  }
#endif
}

bool
TokenizerIgnoreNothing(char16_t /* aChar */)
{
  return false;
}

} // anonymous namespace

StaticAutoPtr<DeviceStorageUsedSpaceCache>
  DeviceStorageUsedSpaceCache::sDeviceStorageUsedSpaceCache;

DeviceStorageUsedSpaceCache::DeviceStorageUsedSpaceCache()
{
  MOZ_ASSERT(NS_IsMainThread());

  mIOThread = new LazyIdleThread(
    DEFAULT_THREAD_TIMEOUT_MS,
    NS_LITERAL_CSTRING("DeviceStorageUsedSpaceCache I/O"));

}

DeviceStorageUsedSpaceCache::~DeviceStorageUsedSpaceCache()
{
}

DeviceStorageUsedSpaceCache*
DeviceStorageUsedSpaceCache::CreateOrGet()
{
  if (sDeviceStorageUsedSpaceCache) {
    return sDeviceStorageUsedSpaceCache;
  }

  MOZ_ASSERT(NS_IsMainThread());

  sDeviceStorageUsedSpaceCache = new DeviceStorageUsedSpaceCache();
  ClearOnShutdown(&sDeviceStorageUsedSpaceCache);
  return sDeviceStorageUsedSpaceCache;
}

already_AddRefed<DeviceStorageUsedSpaceCache::CacheEntry>
DeviceStorageUsedSpaceCache::GetCacheEntry(const nsAString& aStorageName)
{
  nsTArray<RefPtr<CacheEntry>>::size_type numEntries = mCacheEntries.Length();
  nsTArray<RefPtr<CacheEntry>>::index_type i;
  for (i = 0; i < numEntries; i++) {
    RefPtr<CacheEntry>& cacheEntry = mCacheEntries[i];
    if (cacheEntry->mStorageName.Equals(aStorageName)) {
      RefPtr<CacheEntry> addRefedCacheEntry = cacheEntry;
      return addRefedCacheEntry.forget();
    }
  }
  return nullptr;
}

static int64_t
GetFreeBytes(const nsAString& aStorageName)
{
  // This function makes the assumption that the various types
  // are all stored on the same filesystem. So we use pictures.

  RefPtr<DeviceStorageFile> dsf(new DeviceStorageFile(NS_LITERAL_STRING(DEVICESTORAGE_PICTURES),
                                                        aStorageName));
  int64_t freeBytes = 0;
  dsf->GetStorageFreeSpace(&freeBytes);
  return freeBytes;
}

nsresult
DeviceStorageUsedSpaceCache::AccumUsedSizes(const nsAString& aStorageName,
                                            uint64_t* aPicturesSoFar,
                                            uint64_t* aVideosSoFar,
                                            uint64_t* aMusicSoFar,
                                            uint64_t* aTotalSoFar)
{
  RefPtr<CacheEntry> cacheEntry = GetCacheEntry(aStorageName);
  if (!cacheEntry || cacheEntry->mDirty) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  int64_t freeBytes = GetFreeBytes(cacheEntry->mStorageName);
  if (freeBytes != cacheEntry->mFreeBytes) {
    // Free space changed, so our cached results are no longer valid.
    return NS_ERROR_NOT_AVAILABLE;
  }

  *aPicturesSoFar += cacheEntry->mPicturesUsedSize;
  *aVideosSoFar += cacheEntry->mVideosUsedSize;
  *aMusicSoFar += cacheEntry->mMusicUsedSize;
  *aTotalSoFar += cacheEntry->mTotalUsedSize;

  return NS_OK;
}

void
DeviceStorageUsedSpaceCache::SetUsedSizes(const nsAString& aStorageName,
                                          uint64_t aPictureSize,
                                          uint64_t aVideosSize,
                                          uint64_t aMusicSize,
                                          uint64_t aTotalUsedSize)
{
  RefPtr<CacheEntry> cacheEntry = GetCacheEntry(aStorageName);
  if (!cacheEntry) {
    cacheEntry = new CacheEntry;
    cacheEntry->mStorageName = aStorageName;
    mCacheEntries.AppendElement(cacheEntry);
  }
  cacheEntry->mFreeBytes = GetFreeBytes(cacheEntry->mStorageName);

  cacheEntry->mPicturesUsedSize = aPictureSize;
  cacheEntry->mVideosUsedSize = aVideosSize;
  cacheEntry->mMusicUsedSize = aMusicSize;
  cacheEntry->mTotalUsedSize = aTotalUsedSize;
  cacheEntry->mDirty = false;
}

StaticAutoPtr<DeviceStorageTypeChecker>
  DeviceStorageTypeChecker::sDeviceStorageTypeChecker;

DeviceStorageTypeChecker::DeviceStorageTypeChecker()
{
}

DeviceStorageTypeChecker::~DeviceStorageTypeChecker()
{
}

DeviceStorageTypeChecker*
DeviceStorageTypeChecker::CreateOrGet()
{
  if (sDeviceStorageTypeChecker) {
    return sDeviceStorageTypeChecker;
  }

  MOZ_ASSERT(NS_IsMainThread());

  nsCOMPtr<nsIStringBundleService> stringService
    = mozilla::services::GetStringBundleService();
  if (!stringService) {
    return nullptr;
  }

  nsCOMPtr<nsIStringBundle> filterBundle;
  if (NS_FAILED(stringService->CreateBundle(DEVICESTORAGE_PROPERTIES,
                                            getter_AddRefs(filterBundle)))) {
    return nullptr;
  }

  DeviceStorageTypeChecker* result = new DeviceStorageTypeChecker();
  result->InitFromBundle(filterBundle);

  sDeviceStorageTypeChecker = result;
  ClearOnShutdown(&sDeviceStorageTypeChecker);
  return result;
}

void
DeviceStorageTypeChecker::InitFromBundle(nsIStringBundle* aBundle)
{
  aBundle->GetStringFromName(
    NS_ConvertASCIItoUTF16(DEVICESTORAGE_PICTURES).get(),
    getter_Copies(mPicturesExtensions));
  aBundle->GetStringFromName(
    NS_ConvertASCIItoUTF16(DEVICESTORAGE_MUSIC).get(),
    getter_Copies(mMusicExtensions));
  aBundle->GetStringFromName(
    NS_ConvertASCIItoUTF16(DEVICESTORAGE_VIDEOS).get(),
    getter_Copies(mVideosExtensions));
}


bool
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/"));
  }

  if (aType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
    return StringBeginsWith(mimeType, NS_LITERAL_STRING("video/"));
  }

  if (aType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
    return StringBeginsWith(mimeType, NS_LITERAL_STRING("audio/"));
  }

  if (aType.EqualsLiteral(DEVICESTORAGE_APPS) ||
      aType.EqualsLiteral(DEVICESTORAGE_SDCARD) ||
      aType.EqualsLiteral(DEVICESTORAGE_CRASHES)) {
    // Apps, crashes and sdcard have no restriction on mime types
    return true;
  }

  return false;
}

bool
DeviceStorageTypeChecker::Check(const nsAString& aType, nsIFile* aFile)
{
  if (!aFile) {
    return false;
  }

  nsString path;
  aFile->GetPath(path);

  return Check(aType, path);
}

bool
DeviceStorageTypeChecker::Check(const nsAString& aType, const nsString& aPath)
{
  if (aType.EqualsLiteral(DEVICESTORAGE_APPS) ||
      aType.EqualsLiteral(DEVICESTORAGE_SDCARD) ||
      aType.EqualsLiteral(DEVICESTORAGE_CRASHES)) {
    // Apps, crashes and sdcard have no restrictions on what file extensions used.
    return true;
  }

  int32_t dotIdx = aPath.RFindChar(char16_t('.'));
  if (dotIdx == kNotFound) {
    return false;
  }

  nsAutoString extensionMatch;
  extensionMatch.Assign('*');
  extensionMatch.Append(Substring(aPath, dotIdx));
  extensionMatch.Append(';');

  if (aType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
    return CaseInsensitiveFindInReadable(extensionMatch, mPicturesExtensions);
  }

  if (aType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
    return CaseInsensitiveFindInReadable(extensionMatch, mVideosExtensions);
  }

  if (aType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
    return CaseInsensitiveFindInReadable(extensionMatch, mMusicExtensions);
  }

  return false;
}

void
DeviceStorageTypeChecker::GetTypeFromFile(nsIFile* aFile, nsAString& aType)
{
  MOZ_ASSERT(aFile);

  nsString path;
  aFile->GetPath(path);

  GetTypeFromFileName(path, aType);
}

void
DeviceStorageTypeChecker::GetTypeFromFileName(const nsAString& aFileName,
                                              nsAString& aType)
{
  aType.AssignLiteral(DEVICESTORAGE_SDCARD);

  nsString fileName(aFileName);
  int32_t dotIdx = fileName.RFindChar(char16_t('.'));
  if (dotIdx == kNotFound) {
    return;
  }

  nsAutoString extensionMatch;
  extensionMatch.Assign('*');
  extensionMatch.Append(Substring(aFileName, dotIdx));
  extensionMatch.Append(';');

  if (CaseInsensitiveFindInReadable(extensionMatch, mPicturesExtensions)) {
    aType.AssignLiteral(DEVICESTORAGE_PICTURES);
  }
  else if (CaseInsensitiveFindInReadable(extensionMatch, mVideosExtensions)) {
    aType.AssignLiteral(DEVICESTORAGE_VIDEOS);
  }
  else if (CaseInsensitiveFindInReadable(extensionMatch, mMusicExtensions)) {
    aType.AssignLiteral(DEVICESTORAGE_MUSIC);
  }
}

nsresult
DeviceStorageTypeChecker::GetPermissionForType(const nsAString& aType,
                                               nsACString& aPermissionResult)
{
  if (!aType.EqualsLiteral(DEVICESTORAGE_PICTURES) &&
      !aType.EqualsLiteral(DEVICESTORAGE_VIDEOS) &&
      !aType.EqualsLiteral(DEVICESTORAGE_MUSIC) &&
      !aType.EqualsLiteral(DEVICESTORAGE_APPS) &&
      !aType.EqualsLiteral(DEVICESTORAGE_SDCARD) &&
      !aType.EqualsLiteral(DEVICESTORAGE_CRASHES)) {
    // unknown type
    return NS_ERROR_FAILURE;
  }

  aPermissionResult.AssignLiteral("device-storage:");
  aPermissionResult.Append(NS_ConvertUTF16toUTF8(aType));
  return NS_OK;
}

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:
    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:
      return DEVICE_STORAGE_ACCESS_WRITE;
    case DEVICE_STORAGE_REQUEST_CREATE:
    case DEVICE_STORAGE_REQUEST_CREATEFD:
      return DEVICE_STORAGE_ACCESS_CREATE;
    default:
      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) ||
         aType.EqualsLiteral(DEVICESTORAGE_SDCARD);
}

//static
bool
DeviceStorageTypeChecker::IsVolumeBased(const nsAString& aType)
{
#ifdef MOZ_WIDGET_GONK
  // The apps and crashes aren't stored in the same place as the media, so
  // we only ever return a single apps object, and not an array
  // with one per volume (as is the case for the remaining
  // storage types).
  return IsMediaType(aType);
#else
  return false;
#endif
}

//static
bool
DeviceStorageTypeChecker::IsSharedMediaRoot(const nsAString& aType)
{
  // This function determines if aType shares a root directory with the
  // other media types (so only applies to music, videos, pictures and sdcard).
#ifdef MOZ_WIDGET_GONK
  return IsMediaType(aType);
#else
  // For desktop, if the directories have been overridden, then they share
  // a common root.
  return IsMediaType(aType) && DeviceStorageStatics::HasOverrideRootDir();
#endif
}

class IOEventComplete : public Runnable
{
public:
  IOEventComplete(DeviceStorageFile *aFile, const char *aType)
    : mFile(aFile)
    , mType(aType)
  {
  }

  ~IOEventComplete() {}

  NS_IMETHOD Run()
  {
    MOZ_ASSERT(NS_IsMainThread());
    nsString data;
    CopyASCIItoUTF16(mType, data);
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();

    obs->NotifyObservers(mFile, "file-watcher-notify", data.get());

    DeviceStorageUsedSpaceCache* usedSpaceCache
      = DeviceStorageUsedSpaceCache::CreateOrGet();
    MOZ_ASSERT(usedSpaceCache);
    usedSpaceCache->Invalidate(mFile->mStorageName);
    return NS_OK;
  }

private:
  RefPtr<DeviceStorageFile> mFile;
  nsCString mType;
};

DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType,
                                     const nsAString& aStorageName,
                                     const nsAString& aRootDir,
                                     const nsAString& aPath)
  : mStorageType(aStorageType)
  , mStorageName(aStorageName)
  , mRootDir(aRootDir)
  , mPath(aPath)
  , mEditable(false)
  , mLength(UINT64_MAX)
  , mLastModifiedDate(UINT64_MAX)
{
  Init();
  AppendRelativePath(mRootDir);
  if (!mPath.EqualsLiteral("")) {
    AppendRelativePath(mPath);
  }

  NormalizeFilePath(mPath);
}

DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType,
                                     const nsAString& aStorageName,
                                     const nsAString& aPath)
  : mStorageType(aStorageType)
  , mStorageName(aStorageName)
  , mPath(aPath)
  , mEditable(false)
  , mLength(UINT64_MAX)
  , mLastModifiedDate(UINT64_MAX)
{
  Init();
  AppendRelativePath(aPath);
  NormalizeFilePath(mPath);
}

DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType,
                                     const nsAString& aStorageName)
  : mStorageType(aStorageType)
  , mStorageName(aStorageName)
  , mEditable(false)
  , mLength(UINT64_MAX)
  , mLastModifiedDate(UINT64_MAX)
{
  Init();
}

void
DeviceStorageFile::Dump(const char* label)
{
  nsString path;
  if (mFile) {
    mFile->GetPath(path);
  } else {
    path = NS_LITERAL_STRING("(null)");
  }
  const char* ptStr;
  if (XRE_IsParentProcess()) {
    ptStr = "parent";
  } else {
    ptStr = "child";
  }

  printf_stderr("DSF (%s) %s: mStorageType '%s' mStorageName '%s' "
                "mRootDir '%s' mPath '%s' mFile->GetPath '%s'\n",
                ptStr, label,
                NS_LossyConvertUTF16toASCII(mStorageType).get(),
                NS_LossyConvertUTF16toASCII(mStorageName).get(),
                NS_LossyConvertUTF16toASCII(mRootDir).get(),
                NS_LossyConvertUTF16toASCII(mPath).get(),
                NS_LossyConvertUTF16toASCII(path).get());
}

void
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('/');
    aFullPath.Append(mStorageName);
    aFullPath.Append('/');
  }
  if (!mRootDir.EqualsLiteral("")) {
    aFullPath.Append(mRootDir);
    aFullPath.Append('/');
  }
  aFullPath.Append(mPath);
}


// Directories which don't depend on a volume should be calculated once
// in DeviceStorageStatics::Initialize. Directories which depend on the
// root directory of a volume should be calculated in this method.
void
DeviceStorageFile::GetRootDirectoryForType(const nsAString& aStorageType,
                                           const nsAString& aStorageName,
                                           nsIFile** aFile)
{
  nsCOMPtr<nsIFile> f;
  *aFile = nullptr;

  DeviceStorageStatics::InitializeDirs();

#ifdef MOZ_WIDGET_GONK
  nsresult rv;
  nsString volMountPoint;
  if (DeviceStorageTypeChecker::IsVolumeBased(aStorageType)) {
    nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
    NS_ENSURE_TRUE_VOID(vs);
    nsCOMPtr<nsIVolume> vol;
    rv = vs->GetVolumeByName(aStorageName, getter_AddRefs(vol));
    if (NS_FAILED(rv)) {
      printf_stderr("##### DeviceStorage: GetVolumeByName('%s') failed\n",
                    NS_LossyConvertUTF16toASCII(aStorageName).get());
    }
    NS_ENSURE_SUCCESS_VOID(rv);
    vol->GetMountPoint(volMountPoint);
  }
#endif

  if (aStorageType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
    f = DeviceStorageStatics::GetPicturesDir();
  } else if (aStorageType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
    f = DeviceStorageStatics::GetVideosDir();
  } else if (aStorageType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
    f = DeviceStorageStatics::GetMusicDir();
  } else if (aStorageType.EqualsLiteral(DEVICESTORAGE_APPS)) {
    f = DeviceStorageStatics::GetAppsDir();
  } else if (aStorageType.EqualsLiteral(DEVICESTORAGE_CRASHES)) {
    f = DeviceStorageStatics::GetCrashesDir();
  } else if (aStorageType.EqualsLiteral(DEVICESTORAGE_SDCARD)) {
    f = DeviceStorageStatics::GetSdcardDir();
  } else {
    printf_stderr("##### DeviceStorage: Unrecognized StorageType: '%s'\n",
                  NS_LossyConvertUTF16toASCII(aStorageType).get());
    return;
  }

#ifdef MOZ_WIDGET_GONK
  /* For volume based storage types, we will only have a file already
     if the override root directory option is in effect. */
  if (!f && !volMountPoint.IsEmpty()) {
    rv = NS_NewLocalFile(volMountPoint, false, getter_AddRefs(f));
    if (NS_FAILED(rv)) {
      printf_stderr("##### DeviceStorage: NS_NewLocalFile failed StorageType: '%s' path '%s'\n",
                    NS_LossyConvertUTF16toASCII(volMountPoint).get(),
                    NS_LossyConvertUTF16toASCII(aStorageType).get());
    }
  }
#endif

  if (f) {
    f->Clone(aFile);
  } else {
    // This should never happen unless something is severely wrong. So
    // scream a little.
    printf_stderr("##### GetRootDirectoryForType('%s', '%s') failed #####",
                  NS_LossyConvertUTF16toASCII(aStorageType).get(),
                  NS_LossyConvertUTF16toASCII(aStorageName).get());
  }
}

//static
already_AddRefed<DeviceStorageFile>
DeviceStorageFile::CreateUnique(nsAString& aFileName,
                                uint32_t aFileType,
                                uint32_t aFileAttributes)
{
  DeviceStorageTypeChecker* typeChecker
    = DeviceStorageTypeChecker::CreateOrGet();
  MOZ_ASSERT(typeChecker);

  nsString storageType;
  typeChecker->GetTypeFromFileName(aFileName, storageType);

  nsString storageName;
  nsString storagePath;
  if (!nsDOMDeviceStorage::ParseFullPath(aFileName, storageName, storagePath)) {
    return nullptr;
  }
  if (storageName.IsEmpty()) {
    nsDOMDeviceStorage::GetDefaultStorageName(storageType, storageName);
  }
  return CreateUnique(storageType, storageName, storagePath,
                      aFileType, aFileAttributes);
}

//static
already_AddRefed<DeviceStorageFile>
DeviceStorageFile::CreateUnique(const nsAString& aStorageType,
                                const nsAString& aStorageName,
                                nsAString& aFileName,
                                uint32_t aFileType,
                                uint32_t aFileAttributes)
{
  RefPtr<DeviceStorageFile> dsf =
    new DeviceStorageFile(aStorageType, aStorageName, aFileName);
  if (!dsf->mFile) {
    return nullptr;
  }

  nsresult rv = dsf->mFile->CreateUnique(aFileType, aFileAttributes);
  NS_ENSURE_SUCCESS(rv, nullptr);

  // CreateUnique may cause the filename to change. So we need to update mPath
  // to reflect that.
  nsString leafName;
  dsf->mFile->GetLeafName(leafName);

  int32_t lastSlashIndex = dsf->mPath.RFindChar('/');
  if (lastSlashIndex == kNotFound) {
    dsf->mPath.Assign(leafName);
  } else {
    // Include the last '/'
    dsf->mPath = Substring(dsf->mPath, 0, lastSlashIndex + 1);
    dsf->mPath.Append(leafName);
  }

  return dsf.forget();
}

void
DeviceStorageFile::SetPath(const nsAString& aPath) {
  mPath.Assign(aPath);
  NormalizeFilePath(mPath);
}

void
DeviceStorageFile::SetEditable(bool aEditable) {
  mEditable = aEditable;
}

// we want to make sure that the names of file can't reach
// outside of the type of storage the user asked for.
bool
DeviceStorageFile::IsSafePath() const
{
  return ValidateAndSplitPath(mRootDir) && ValidateAndSplitPath(mPath);
}

bool
DeviceStorageFile::ValidateAndSplitPath(const nsAString& aPath,
                                        nsTArray<nsString>* aParts) const
{
  nsAString::const_iterator start, end;
  aPath.BeginReading(start);
  aPath.EndReading(end);

  // if the path is a '~' or starts with '~/', return false.
  NS_NAMED_LITERAL_STRING(tilde, "~");
  NS_NAMED_LITERAL_STRING(tildeSlash, "~/");
  if (aPath.Equals(tilde) ||
      StringBeginsWith(aPath, tildeSlash)) {
    NS_WARNING("Path name starts with tilde!");
    return false;
  }

  NS_NAMED_LITERAL_STRING(kCurrentDir, ".");
  NS_NAMED_LITERAL_STRING(kParentDir, "..");

  // Split path and check each path component.
  nsCharSeparatedTokenizerTemplate<TokenizerIgnoreNothing>
    tokenizer(aPath, FILESYSTEM_DOM_PATH_SEPARATOR_CHAR);

  while (tokenizer.hasMoreTokens()) {
    nsDependentSubstring pathComponent = tokenizer.nextToken();
    // The path containing empty components, such as "foo//bar", is invalid.
    // We don't allow paths, such as "../foo", "foo/./bar" and "foo/../bar",
    // to walk up the directory.
    if (pathComponent.IsEmpty() ||
        pathComponent.Equals(kCurrentDir) ||
        pathComponent.Equals(kParentDir)) {
      return false;
    }

    if (aParts) {
      aParts->AppendElement(pathComponent);
    }
  }
  return true;
}

void
DeviceStorageFile::AppendRelativePath(const nsAString& aPath)
{
  if (!mFile) {
    return;
  }

  nsTArray<nsString> parts;

  if (!ValidateAndSplitPath(aPath, &parts)) {
    // All of the APIs (in the child) do checks to verify that the path is
    // valid and return PERMISSION_DENIED if a non-safe path is entered.
    // This check is done in the parent and prevents a compromised
    // child from bypassing the check. It shouldn't be possible for this
    // code path to be taken with a non-compromised child.
    NS_WARNING("Unsafe path detected - ignoring");
    NS_WARNING(NS_LossyConvertUTF16toASCII(aPath).get());
    return;
  }

  for (uint32_t i = 0; i < parts.Length(); ++i) {
    nsresult rv = mFile->AppendRelativePath(parts[i]);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return;
    }
  }
}

nsresult
DeviceStorageFile::CreateFileDescriptor(FileDescriptor& aFileDescriptor)
{
  if (!mFile) {
    return NS_ERROR_FAILURE;
  }
  ScopedPRFileDesc fd;
  nsresult rv = mFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE,
                                        0660, &fd.rwget());
  NS_ENSURE_SUCCESS(rv, rv);

  // NOTE: The FileDescriptor::PlatformHandleType constructor returns a dup of
  //       the file descriptor, so we don't need the original fd after this.
  //       Our scoped file descriptor will automatically close fd.
  aFileDescriptor = FileDescriptor(
    FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(fd)));
  return NS_OK;
}

nsresult
DeviceStorageFile::Write(nsIInputStream* aInputStream)
{
  if (!aInputStream || !mFile) {
    return NS_ERROR_FAILURE;
  }

  nsresult rv = mFile->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = NS_DispatchToMainThread(new IOEventComplete(this, "created"));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsCOMPtr<nsIOutputStream> outputStream;
  NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mFile);

  if (!outputStream) {
    return NS_ERROR_FAILURE;
  }

  return Append(aInputStream, outputStream);
}

nsresult
DeviceStorageFile::Write(InfallibleTArray<uint8_t>& aBits)
{
  if (!mFile) {
    return NS_ERROR_FAILURE;
  }

  nsresult rv = mFile->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = NS_DispatchToMainThread(new IOEventComplete(this, "created"));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsCOMPtr<nsIOutputStream> outputStream;
  NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mFile);

  if (!outputStream) {
    return NS_ERROR_FAILURE;
  }

  uint32_t wrote;
  outputStream->Write((char*) aBits.Elements(), aBits.Length(), &wrote);
  outputStream->Close();

  rv = NS_DispatchToMainThread(new IOEventComplete(this, "modified"));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  if (aBits.Length() != wrote) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

nsresult
DeviceStorageFile::Append(nsIInputStream* aInputStream)
{
  if (!aInputStream || !mFile) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIOutputStream> outputStream;
  NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mFile, PR_WRONLY | PR_CREATE_FILE | PR_APPEND, -1, 0);

  if (!outputStream) {
    return NS_ERROR_FAILURE;
  }

  return Append(aInputStream, outputStream);
}


nsresult
DeviceStorageFile::Append(nsIInputStream* aInputStream, nsIOutputStream* aOutputStream)
{
  uint64_t bufSize = 0;
  aInputStream->Available(&bufSize);

  nsCOMPtr<nsIOutputStream> bufferedOutputStream;
  nsresult rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream),
                                  aOutputStream,
                                  4096*4);
  NS_ENSURE_SUCCESS(rv, rv);

  while (bufSize) {
    uint32_t wrote;
    rv = bufferedOutputStream->WriteFrom(
      aInputStream,
        static_cast<uint32_t>(std::min<uint64_t>(bufSize, UINT32_MAX)),
        &wrote);
    if (NS_FAILED(rv)) {
      break;
    }
    bufSize -= wrote;
  }

  rv = NS_DispatchToMainThread(new IOEventComplete(this, "modified"));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  bufferedOutputStream->Close();
  aOutputStream->Close();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

nsresult
DeviceStorageFile::Remove()
{
  MOZ_ASSERT(!NS_IsMainThread());

  if (!mFile) {
    return NS_ERROR_FAILURE;
  }

  bool check;
  nsresult rv = mFile->Exists(&check);
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (!check) {
    return NS_OK;
  }

  rv = mFile->Remove(true);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return NS_DispatchToMainThread(new IOEventComplete(this, "deleted"));
}

nsresult
DeviceStorageFile::CalculateMimeType()
{
  MOZ_ASSERT(NS_IsMainThread());

  if (!mFile) {
    return NS_ERROR_FAILURE;
  }
  nsAutoCString mimeType;
  nsCOMPtr<nsIMIMEService> mimeService =
    do_GetService(NS_MIMESERVICE_CONTRACTID);
  if (mimeService) {
    nsresult rv = mimeService->GetTypeFromFile(mFile, mimeType);
    if (NS_FAILED(rv)) {
      mimeType.Truncate();
      return rv;
    }
  }

  mMimeType = NS_ConvertUTF8toUTF16(mimeType);
  return NS_OK;
}

nsresult
DeviceStorageFile::CalculateSizeAndModifiedDate()
{
  MOZ_ASSERT(!NS_IsMainThread());

  if (!mFile) {
    return NS_ERROR_FAILURE;
  }

  int64_t fileSize;
  nsresult rv = mFile->GetFileSize(&fileSize);
  NS_ENSURE_SUCCESS(rv, rv);

  mLength = fileSize;

  PRTime modDate;
  rv = mFile->GetLastModifiedTime(&modDate);
  NS_ENSURE_SUCCESS(rv, rv);

  mLastModifiedDate = modDate;
  return NS_OK;
}

void
DeviceStorageFile::CollectFiles(nsTArray<RefPtr<DeviceStorageFile> > &aFiles,
                                PRTime aSince)
{
  if (!mFile) {
    return;
  }
  nsString fullRootPath;
  mFile->GetPath(fullRootPath);
  collectFilesInternal(aFiles, aSince, fullRootPath);
}

void
DeviceStorageFile::collectFilesInternal(
  nsTArray<RefPtr<DeviceStorageFile> > &aFiles,
  PRTime aSince,
  nsAString& aRootPath)
{
  if (!mFile || !IsAvailable()) {
    return;
  }

  nsCOMPtr<nsISimpleEnumerator> e;
  mFile->GetDirectoryEntries(getter_AddRefs(e));

  if (!e) {
    return;
  }

  DeviceStorageTypeChecker* typeChecker
    = DeviceStorageTypeChecker::CreateOrGet();
  MOZ_ASSERT(typeChecker);

  nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
  nsCOMPtr<nsIFile> f;

  while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(f))) && f) {

    bool isFile;
    f->IsFile(&isFile);

    if (isFile) {
      PRTime msecs;
      f->GetLastModifiedTime(&msecs);

      if (msecs < aSince) {
        continue;
      }
    }

    bool isDir;
    f->IsDirectory(&isDir);

    nsString fullpath;
    nsresult rv = f->GetPath(fullpath);
    if (NS_FAILED(rv)) {
      continue;
    }

    if (isFile && !typeChecker->Check(mStorageType, fullpath)) {
      continue;
    }

    if (!StringBeginsWith(fullpath, aRootPath)) {
      NS_ERROR("collectFiles returned a path that does not belong!");
      continue;
    }

    nsAString::size_type len = aRootPath.Length() + 1; // +1 for the trailing /
    nsDependentSubstring newPath = Substring(fullpath, len);

    if (isDir) {
      DeviceStorageFile dsf(mStorageType, mStorageName, mRootDir, newPath);
      dsf.collectFilesInternal(aFiles, aSince, aRootPath);
    } else if (isFile) {
      RefPtr<DeviceStorageFile> dsf =
        new DeviceStorageFile(mStorageType, mStorageName, mRootDir, newPath);
      dsf->CalculateSizeAndModifiedDate();
      aFiles.AppendElement(dsf);
    }
  }
}

void
DeviceStorageFile::AccumDiskUsage(uint64_t* aPicturesSoFar,
                                  uint64_t* aVideosSoFar,
                                  uint64_t* aMusicSoFar,
                                  uint64_t* aTotalSoFar)
{
  if (!IsAvailable()) {
    return;
  }

  uint64_t pictureUsage = 0, videoUsage = 0, musicUsage = 0, totalUsage = 0;

  if (DeviceStorageTypeChecker::IsVolumeBased(mStorageType)) {
    DeviceStorageUsedSpaceCache* usedSpaceCache =
      DeviceStorageUsedSpaceCache::CreateOrGet();
    MOZ_ASSERT(usedSpaceCache);
    nsresult rv = usedSpaceCache->AccumUsedSizes(mStorageName,
                                                 aPicturesSoFar, aVideosSoFar,
                                                 aMusicSoFar, aTotalSoFar);
    if (NS_SUCCEEDED(rv)) {
      return;
    }
    AccumDirectoryUsage(mFile, &pictureUsage, &videoUsage,
                        &musicUsage, &totalUsage);
    usedSpaceCache->SetUsedSizes(mStorageName, pictureUsage, videoUsage,
                                 musicUsage, totalUsage);
  } else {
    AccumDirectoryUsage(mFile, &pictureUsage, &videoUsage,
                        &musicUsage, &totalUsage);
  }

  *aPicturesSoFar += pictureUsage;
  *aVideosSoFar += videoUsage;
  *aMusicSoFar += musicUsage;
  *aTotalSoFar += totalUsage;
}

void
DeviceStorageFile::AccumDirectoryUsage(nsIFile* aFile,
                                       uint64_t* aPicturesSoFar,
                                       uint64_t* aVideosSoFar,
                                       uint64_t* aMusicSoFar,
                                       uint64_t* aTotalSoFar)
{
  if (!aFile) {
    return;
  }

  nsresult rv;
  nsCOMPtr<nsISimpleEnumerator> e;
  rv = aFile->GetDirectoryEntries(getter_AddRefs(e));

  if (NS_FAILED(rv) || !e) {
    return;
  }

  nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
  MOZ_ASSERT(files);

  nsCOMPtr<nsIFile> f;
  while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(f))) && f) {
    bool isDir;
    rv = f->IsDirectory(&isDir);
    if (NS_FAILED(rv)) {
      continue;
    }

    bool isFile;
    rv = f->IsFile(&isFile);
    if (NS_FAILED(rv)) {
      continue;
    }

    bool isLink;
    rv = f->IsSymlink(&isLink);
    if (NS_FAILED(rv)) {
      continue;
    }

    if (isLink) {
      // for now, lets just totally ignore symlinks.
      NS_WARNING("DirectoryDiskUsage ignores symlinks");
    } else if (isDir) {
      AccumDirectoryUsage(f, aPicturesSoFar, aVideosSoFar,
                          aMusicSoFar, aTotalSoFar);
    } else if (isFile) {

      int64_t size;
      rv = f->GetFileSize(&size);
      if (NS_FAILED(rv)) {
        continue;
      }
      DeviceStorageTypeChecker* typeChecker
        = DeviceStorageTypeChecker::CreateOrGet();
      MOZ_ASSERT(typeChecker);
      nsString type;
      typeChecker->GetTypeFromFile(f, type);

      if (type.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
        *aPicturesSoFar += size;
      }
      else if (type.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
        *aVideosSoFar += size;
      }
      else if (type.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
        *aMusicSoFar += size;
      }
      *aTotalSoFar += size;
    }
  }
}

void
DeviceStorageFile::GetStorageFreeSpace(int64_t* aSoFar)
{
  DeviceStorageTypeChecker* typeChecker
    = DeviceStorageTypeChecker::CreateOrGet();
  if (!typeChecker) {
    return;
  }
  if (!mFile || !IsAvailable()) {
    return;
  }

  int64_t storageAvail = 0;
  nsresult rv = mFile->GetDiskSpaceAvailable(&storageAvail);
  if (NS_SUCCEEDED(rv)) {
    *aSoFar += storageAvail;
  }
}

bool
DeviceStorageFile::IsAvailable()
{
  nsString status;
  GetStatus(status);
  return status.EqualsLiteral("available");
}

void
DeviceStorageFile::DoFormat(nsAString& aStatus)
{
  DeviceStorageTypeChecker* typeChecker
    = DeviceStorageTypeChecker::CreateOrGet();
  if (!typeChecker || !mFile) {
    return;
  }
  if (!typeChecker->IsVolumeBased(mStorageType)) {
    aStatus.AssignLiteral("notVolume");
    return;
  }
#ifdef MOZ_WIDGET_GONK
  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
  NS_ENSURE_TRUE_VOID(vs);

  nsCOMPtr<nsIVolume> vol;
  nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
  NS_ENSURE_SUCCESS_VOID(rv);
  if (!vol) {
    return;
  }

  vol->Format();

  aStatus.AssignLiteral("formatting");
#endif
  return;
}

void
DeviceStorageFile::DoMount(nsAString& aStatus)
{
  DeviceStorageTypeChecker* typeChecker
    = DeviceStorageTypeChecker::CreateOrGet();
  if (!typeChecker || !mFile) {
    return;
  }
  if (!typeChecker->IsVolumeBased(mStorageType)) {
    aStatus.AssignLiteral("notVolume");
    return;
  }
#ifdef MOZ_WIDGET_GONK
  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
  NS_ENSURE_TRUE_VOID(vs);

  nsCOMPtr<nsIVolume> vol;
  nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
  NS_ENSURE_SUCCESS_VOID(rv);
  if (!vol) {
    return;
  }

  vol->Mount();

  aStatus.AssignLiteral("mounting");
#endif
  return;
}

void
DeviceStorageFile::DoUnmount(nsAString& aStatus)
{
  DeviceStorageTypeChecker* typeChecker
    = DeviceStorageTypeChecker::CreateOrGet();
  if (!typeChecker || !mFile) {
    return;
  }
  if (!typeChecker->IsVolumeBased(mStorageType)) {
    aStatus.AssignLiteral("notVolume");
    return;
  }
#ifdef MOZ_WIDGET_GONK
  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
  NS_ENSURE_TRUE_VOID(vs);

  nsCOMPtr<nsIVolume> vol;
  nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
  NS_ENSURE_SUCCESS_VOID(rv);
  if (!vol) {
    return;
  }

  vol->Unmount();

  aStatus.AssignLiteral("unmounting");
#endif
  return;
}

void
DeviceStorageFile::GetStatus(nsAString& aStatus)
{
  aStatus.AssignLiteral("unavailable");

  DeviceStorageTypeChecker* typeChecker
    = DeviceStorageTypeChecker::CreateOrGet();
  if (!typeChecker || !mFile) {
    return;
  }
  if (!typeChecker->IsVolumeBased(mStorageType)) {
    aStatus.AssignLiteral("available");
    return;
  }

#ifdef MOZ_WIDGET_GONK
  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
  NS_ENSURE_TRUE_VOID(vs);

  nsCOMPtr<nsIVolume> vol;
  nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
  NS_ENSURE_SUCCESS_VOID(rv);
  if (!vol) {
    return;
  }
  bool isMediaPresent;
  rv = vol->GetIsMediaPresent(&isMediaPresent);
  NS_ENSURE_SUCCESS_VOID(rv);
  if (!isMediaPresent) {
    return;
  }
  bool isSharing;
  rv = vol->GetIsSharing(&isSharing);
  NS_ENSURE_SUCCESS_VOID(rv);
  if (isSharing) {
    aStatus.AssignLiteral("shared");
    return;
  }
  bool isFormatting;
  rv = vol->GetIsFormatting(&isFormatting);
  NS_ENSURE_SUCCESS_VOID(rv);
  if (isFormatting) {
    aStatus.AssignLiteral("unavailable");
    return;
  }
  bool isUnmounting;
  rv = vol->GetIsUnmounting(&isUnmounting);
  NS_ENSURE_SUCCESS_VOID(rv);
  if (isUnmounting) {
    aStatus.AssignLiteral("unavailable");
    return;
  }
  int32_t volState;
  rv = vol->GetState(&volState);
  NS_ENSURE_SUCCESS_VOID(rv);
  if (volState == nsIVolume::STATE_MOUNTED) {
    aStatus.AssignLiteral("available");
  }
#endif
}

void
DeviceStorageFile::GetStorageStatus(nsAString& aStatus)
{
  aStatus.AssignLiteral("undefined");

  DeviceStorageTypeChecker* typeChecker
    = DeviceStorageTypeChecker::CreateOrGet();
  if (!typeChecker || !mFile) {
    return;
  }
  if (!typeChecker->IsVolumeBased(mStorageType)) {
    aStatus.AssignLiteral("available");
    return;
  }

#ifdef MOZ_WIDGET_GONK
  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
  NS_ENSURE_TRUE_VOID(vs);

  nsCOMPtr<nsIVolume> vol;
  nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
  NS_ENSURE_SUCCESS_VOID(rv);
  if (!vol) {
    return;
  }

  int32_t volState;
  rv = vol->GetState(&volState);
  NS_ENSURE_SUCCESS_VOID(rv);
  aStatus.AssignASCII(mozilla::system::NS_VolumeStateStr(volState));
#endif
}

NS_IMPL_ISUPPORTS0(DeviceStorageFile)

void
nsDOMDeviceStorage::SetRootDirectoryForType(const nsAString& aStorageType,
                                            const nsAString& aStorageName)
{
  MOZ_ASSERT(NS_IsMainThread());

  nsCOMPtr<nsIFile> f;
  DeviceStorageFile::GetRootDirectoryForType(aStorageType,
                                             aStorageName,
                                             getter_AddRefs(f));
  mRootDirectory = f;
  mStorageType = aStorageType;
  mStorageName = aStorageName;
}

nsDOMDeviceStorageCursor::nsDOMDeviceStorageCursor(nsIGlobalObject* aGlobal,
                                                   DeviceStorageCursorRequest* aRequest)
  : DOMCursor(aGlobal, nullptr)
  , mOkToCallContinue(false)
  , mRequest(aRequest)
{
}

nsDOMDeviceStorageCursor::~nsDOMDeviceStorageCursor()
{
}

void
nsDOMDeviceStorageCursor::FireSuccess(JS::Handle<JS::Value> aResult)
{
  mOkToCallContinue = true;
  DOMCursor::FireSuccess(aResult);
}

void
nsDOMDeviceStorageCursor::FireDone()
{
  mRequest = nullptr;
  DOMCursor::FireDone();
}

void
nsDOMDeviceStorageCursor::FireError(const nsString& aReason)
{
  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 || !mRequest) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return;
  }

  if (!mResult.isUndefined()) {
    // We call onsuccess multiple times. Clear the last
    // result.
    mResult.setUndefined();
    mDone = false;
  }

  mOkToCallContinue = false;
  aRv = mRequest->Continue();
}

DeviceStorageRequest::DeviceStorageRequest()
  : mId(DeviceStorageRequestManager::INVALID_ID)
  , mAccess(DEVICE_STORAGE_ACCESS_UNDEFINED)
  , mSendToParent(true)
  , mUseMainThread(false)
  , 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,
                                 already_AddRefed<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,
                                 already_AddRefed<DeviceStorageFile>&& aFile,
                                 uint32_t aRequest,
                                 BlobImpl* aBlob)
{
  Initialize(aManager, Move(aFile), aRequest);
  mBlob = aBlob;
  mCheckBlob = true;
  mCheckFile = true;
  MOZ_ASSERT(mBlob);
}

void
DeviceStorageRequest::Initialize(DeviceStorageRequestManager* aManager,
                                 already_AddRefed<DeviceStorageFile>&& aFile,
                                 uint32_t aRequest,
                                 DeviceStorageFileDescriptor* aDSFileDescriptor)
{
  Initialize(aManager, Move(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()
{
  if (mUseMainThread && !NS_IsMainThread()) {
    RefPtr<DeviceStorageRequest> self = this;
    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
    {
      self->Allow();
    });
    return NS_DispatchToMainThread(r.forget());
  }

  nsresult rv = AllowInternal();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    const char *reason;
    switch (rv) {
      case NS_ERROR_ILLEGAL_VALUE:
        reason = POST_ERROR_EVENT_ILLEGAL_TYPE;
        break;
      case NS_ERROR_DOM_SECURITY_ERR:
        reason = POST_ERROR_EVENT_PERMISSION_DENIED;
        break;
      default:
        reason = POST_ERROR_EVENT_UNKNOWN;
        break;
    }
    return Reject(reason);
  }
  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;
}

nsresult
DeviceStorageRequest::Prepare()
{
  return NS_OK;
}

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());

  nsresult rv = Prepare();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  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);
    nsCOMPtr<nsIRunnable> self = this;
    return target->Dispatch(self.forget(), 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()) {
    RefPtr<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.forget());
  }

  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,
                                       already_AddRefed<DeviceStorageFile>&& aFile,
                                       uint32_t aRequest,
                                       PRTime aSince)
{
  Initialize(aManager, Move(aFile), aRequest);
  mStorageType = mFile->mStorageType;
  mSince = aSince;
}

void
DeviceStorageCursorRequest::AddFiles(size_t aSize)
{
  mFiles.SetCapacity(mFiles.Length() + aSize);
}

void
DeviceStorageCursorRequest::AddFile(already_AddRefed<DeviceStorageFile> aFile)
{
  mFiles.AppendElement(aFile);
}

nsresult
DeviceStorageCursorRequest::SendContinueToParentProcess()
{
  if (!NS_IsMainThread()) {
    RefPtr<DeviceStorageCursorRequest> self = this;
    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
    {
      self->SendContinueToParentProcess();
    });
    return NS_DispatchToMainThread(r.forget());
  }

  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 */
    RefPtr<DeviceStorageCursorRequest> self = this;
    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
    {
      self->Continue();
    });
    nsresult rv = NS_DispatchToMainThread(r.forget());
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return Reject(POST_ERROR_EVENT_UNKNOWN);
    }
    return rv;
  }

  DS_LOG_INFO("%u", mId);

  RefPtr<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
{
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:
  using DeviceStorageRequest::Initialize;

  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);
  }

  void Initialize(DeviceStorageRequestManager* aManager,
                  already_AddRefed<DeviceStorageFile>&& aFile,
                  uint32_t aRequest) override
  {
    DeviceStorageRequest::Initialize(aManager, Move(aFile), aRequest);
    mUseMainThread = mFile->mPath.IsEmpty();
  }

protected:
  nsresult Prepare() override
  {
    if (!mFile->mPath.IsEmpty()) {
      // Checks have already been performed when request was created
      return NS_OK;
    }

    MOZ_ASSERT(NS_IsMainThread());

    nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
    if (!mimeSvc) {
      return NS_ERROR_FAILURE;
    }

    // if mimeType or extension are null, the request will be rejected
    // in DeviceStorageRequest::AllowInternal when the type checker
    // verifies the file path
    nsString mimeType;
    mBlob->GetType(mimeType);

    nsCString extension;
    mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType),
                                 EmptyCString(), extension);

    char buffer[32];
    NS_MakeRandomString(buffer, ArrayLength(buffer) - 1);

    nsAutoString path;
    path.AssignLiteral(buffer);
    path.Append('.');
    path.AppendASCII(extension.get());

    RefPtr<DeviceStorageFile> file
      = DeviceStorageFile::CreateUnique(mFile->mStorageType,
                                        mFile->mStorageName, path,
                                        nsIFile::NORMAL_FILE_TYPE, 00600);
    if (!file) {
      return NS_ERROR_FAILURE;
    }
    if (!file->IsSafePath()) {
      return NS_ERROR_DOM_SECURITY_ERR;
    }

    mFile = file.forget();
    return NS_OK;
  }

  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:
  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
  {
    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()
  {
    mUseMainThread = true;
    mUseStreamTransport = true;
    mCheckFile = true;
    DS_LOG_INFO("");
  }

  void Initialize(DeviceStorageRequestManager* aManager,
                  already_AddRefed<DeviceStorageFile>&& aFile,
                  uint32_t aRequest) override
  {
    DeviceStorageRequest::Initialize(aManager, Move(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:
  nsresult Prepare() override
  {
    MOZ_ASSERT(NS_IsMainThread());
    mFile->CalculateMimeType();
    return NS_OK;
  }

  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);
      nsCOMPtr<nsIRunnable> self = this;
      usedSpaceCache->Dispatch(self.forget());
      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;
    mSendToParent = false;
    DS_LOG_INFO("");
  }

  NS_IMETHOD Run() override
  {
    nsString state = NS_LITERAL_STRING("unavailable");
    if (mFile) {
      mFile->GetStatus(state);
    }
    return Resolve(state);
  }
};

class DeviceStorageStatusRequest final
  : public DeviceStorageRequest
{
public:
  DeviceStorageStatusRequest()
  {
    mAccess = DEVICE_STORAGE_ACCESS_READ;
    mSendToParent = false;
    DS_LOG_INFO("");
  }

  NS_IMETHOD Run() override
  {
    nsString state = NS_LITERAL_STRING("undefined");
    if (mFile) {
      mFile->GetStorageStatus(state);
    }
    return Resolve(state);
  }
};

class DeviceStorageWatchRequest final
  : public DeviceStorageRequest
{
public:
  DeviceStorageWatchRequest()
  {
    mAccess = DEVICE_STORAGE_ACCESS_READ;
    mSendToParent = false;
    DS_LOG_INFO("");
  }

  NS_IMETHOD Run() override
  {
    return Resolve();
  }
};

class DeviceStorageFormatRequest final
  : public DeviceStorageRequest
{
public:
  DeviceStorageFormatRequest()
  {
    mAccess = DEVICE_STORAGE_ACCESS_WRITE;
    DS_LOG_INFO("");
  }

  NS_IMETHOD Run() override
  {
    nsString state = NS_LITERAL_STRING("unavailable");
    if (mFile) {
      mFile->DoFormat(state);
    }
    return Resolve(state);
  }

protected:
  nsresult CreateSendParams(DeviceStorageParams& aParams) override
  {
    DeviceStorageFormatParams params(mFile->mStorageType,
                                     mFile->mStorageName);
    aParams = params;
    return NS_OK;
  }
};

class DeviceStorageMountRequest final
  : public DeviceStorageRequest
{
public:
  DeviceStorageMountRequest()
  {
    mAccess = DEVICE_STORAGE_ACCESS_WRITE;
    DS_LOG_INFO("");
  }

  NS_IMETHOD Run() override
  {
    nsString state = NS_LITERAL_STRING("unavailable");
    if (mFile) {
      mFile->DoMount(state);
    }
    return Resolve(state);
  }

protected:
  nsresult CreateSendParams(DeviceStorageParams& aParams) override
  {
    DeviceStorageMountParams params(mFile->mStorageType,
                                    mFile->mStorageName);
    aParams = params;
    return NS_OK;
  }
};

class DeviceStorageUnmountRequest final
  : public DeviceStorageRequest
{
public:
  DeviceStorageUnmountRequest()
  {
    mAccess = DEVICE_STORAGE_ACCESS_WRITE;
    DS_LOG_INFO("");
  }

  NS_IMETHOD Run() override
  {
    nsString state = NS_LITERAL_STRING("unavailable");
    if (mFile) {
      mFile->DoUnmount(state);
    }
    return Resolve(state);
  }

protected:
  nsresult CreateSendParams(DeviceStorageParams& aParams) override
  {
    DeviceStorageUnmountParams params(mFile->mStorageType,
                                      mFile->mStorageName);
    aParams = params;
    return NS_OK;
  }
};

class DeviceStoragePermissionCheck final
  : public nsIContentPermissionRequest
  , public nsIRunnable
{
public:
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStoragePermissionCheck,
                                           nsIContentPermissionRequest)

  DeviceStoragePermissionCheck(
    already_AddRefed<DeviceStorageRequest>&& aRequest,
    uint64_t aWindowID, const PrincipalInfo &aPrincipalInfo)
    : mRequest(Move(aRequest))
    , mWindowID(aWindowID)
    , mPrincipalInfo(aPrincipalInfo)
  {
    MOZ_ASSERT(mRequest);
  }

  NS_IMETHOD Run() override
  {
    MOZ_ASSERT(NS_IsMainThread());

    if (DeviceStorageStatics::IsPromptTesting()) {
      return Allow(JS::UndefinedHandleValue);
    }

    mWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID)->AsInner();
    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(storageType, type);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    nsCString access;
    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(mozIDOMWindow * *aRequestingWindow) override
  {
    NS_IF_ADDREF(*aRequestingWindow = mWindow);
    return NS_OK;
  }

  NS_IMETHOD GetElement(nsIDOMElement * *aRequestingElement) override
  {
    *aRequestingElement = nullptr;
    return NS_OK;
  }

private:
  nsresult Resolve(bool aResolve)
  {
    mRequest->GetManager()->StorePermission(mRequest->GetAccess(), aResolve);
    mRequest->PermissionCacheMissed();
    if (aResolve) {
      return mRequest->Allow();
    }
    return mRequest->Cancel();
  }

  virtual ~DeviceStoragePermissionCheck()
  { }

  RefPtr<DeviceStorageRequest> mRequest;
  uint64_t mWindowID;
  PrincipalInfo mPrincipalInfo;
  nsCOMPtr<nsPIDOMWindowInner> mWindow;
  nsCOMPtr<nsIPrincipal> mPrincipal;
  nsCOMPtr<nsIContentPermissionRequester> mRequester;
};

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(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));
  else
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)

NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStorage, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorage, DOMEventTargetHelper)

int nsDOMDeviceStorage::sInstanceCount = 0;

nsDOMDeviceStorage::nsDOMDeviceStorage(nsPIDOMWindowInner* aWindow)
  : DOMEventTargetHelper(aWindow)
  , mIsShareable(false)
  , mIsRemovable(false)
  , mInnerWindowID(0)
  , mOwningThread(NS_GetCurrentThread())
{
  MOZ_ASSERT(NS_IsMainThread()); // worker support incomplete
  sInstanceCount++;
  DS_LOG_DEBUG("%p (%d)", this, sInstanceCount);
}

nsresult
nsDOMDeviceStorage::CheckPermission(
  already_AddRefed<DeviceStorageRequest>&& aRequest)
{
  MOZ_ASSERT(mManager);
  RefPtr<DeviceStorageRequest> request(aRequest);
  uint32_t cache = mManager->CheckPermission(request->GetAccess());
  switch (cache) {
    case nsIPermissionManager::ALLOW_ACTION:
      return request->Allow();
    case nsIPermissionManager::DENY_ACTION:
      return request->Cancel();
    case nsIPermissionManager::PROMPT_ACTION:
    default:
    {
      nsCOMPtr<nsIThread> mainThread;
      nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return request->Reject(POST_ERROR_EVENT_UNKNOWN);
      }

      return mainThread->Dispatch(
        MakeAndAddRef<DeviceStoragePermissionCheck>(request.forget(),
                                                    mInnerWindowID,
                                                    *mPrincipalInfo),
        NS_DISPATCH_NORMAL);
    }
  }
}

bool
nsDOMDeviceStorage::IsOwningThread()
{
  bool owner = false;
  mOwningThread->IsOnCurrentThread(&owner);
  return owner;
}

nsresult
nsDOMDeviceStorage::DispatchToOwningThread(
  already_AddRefed<nsIRunnable>&& aRunnable)
{
  return mOwningThread->Dispatch(Move(aRunnable), NS_DISPATCH_NORMAL);
}

/* virtual */ JSObject*
nsDOMDeviceStorage::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
  return DeviceStorageBinding::Wrap(aCx, this, aGivenProto);
}

nsresult
nsDOMDeviceStorage::Init(nsPIDOMWindowInner* 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;
      }
      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))) {
        return rv;
      }
      mIsShareable = !isFake;
      bool isRemovable;
      rv = vol->GetIsHotSwappable(&isRemovable);
      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(nsPIDOMWindowInner* 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;
  }
  nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();

  // the 'apps' type is special.  We only want this exposed
  // if the caller has the "webapps-manage" permission.
  if (aIsAppsStorage) {
    nsCOMPtr<nsIPermissionManager> permissionManager
      = services::GetPermissionManager();
    NS_ENSURE_TRUE(permissionManager, NS_ERROR_FAILURE);

    uint32_t permission;
    nsresult rv
      = 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;
}

void
nsDOMDeviceStorage::Shutdown()
{
  MOZ_ASSERT(IsOwningThread());

  if (mManager) {
    mManager->Shutdown();
    mManager = nullptr;
  }

  if (mFileSystem) {
    mFileSystem->Shutdown();
    mFileSystem = nullptr;
  }

  DeviceStorageStatics::RemoveListener(this);
}

StaticAutoPtr<nsTArray<nsString>> nsDOMDeviceStorage::sVolumeNameCache;

// static
void nsDOMDeviceStorage::InvalidateVolumeCaches()
{
  MOZ_ASSERT(NS_IsMainThread());

  // Currently there is only the one volume cache. DeviceStorageAreaListener
  // calls this function any time it detects a volume was added or removed.

  sVolumeNameCache = nullptr;
}

// static
void
nsDOMDeviceStorage::GetOrderedVolumeNames(
  const nsAString& aType,
  nsDOMDeviceStorage::VolumeNameArray& aVolumeNames)
{
  if (!DeviceStorageTypeChecker::IsVolumeBased(aType)) {
    aVolumeNames.Clear();
    return;
  }
  GetOrderedVolumeNames(aVolumeNames);
}

// static
void
nsDOMDeviceStorage::GetOrderedVolumeNames(
  nsDOMDeviceStorage::VolumeNameArray& aVolumeNames)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (sVolumeNameCache && sVolumeNameCache->Length() > 0) {
    aVolumeNames.AppendElements(*sVolumeNameCache);
    return;
  }
#ifdef MOZ_WIDGET_GONK
  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
  if (vs) {
    nsCOMPtr<nsIArray> volNames;
    vs->GetVolumeNames(getter_AddRefs(volNames));
    uint32_t length = -1;
    volNames->GetLength(&length);
    for (uint32_t i = 0; i < length; i++) {
      nsCOMPtr<nsISupportsString> str = do_QueryElementAt(volNames, i);
      if (str) {
        nsAutoString s;
        if (NS_SUCCEEDED(str->GetData(s)) && !s.IsEmpty()) {
          aVolumeNames.AppendElement(s);
        }
      }
    }

    // If the volume sdcard exists, then we want it to be first.

    VolumeNameArray::index_type sdcardIndex;
    sdcardIndex = aVolumeNames.IndexOf(NS_LITERAL_STRING("sdcard"));
    if (sdcardIndex != VolumeNameArray::NoIndex && sdcardIndex > 0) {
      aVolumeNames.RemoveElementAt(sdcardIndex);
      aVolumeNames.InsertElementAt(0, NS_LITERAL_STRING("sdcard"));
    }
  }
#endif
  if (aVolumeNames.IsEmpty()) {
    aVolumeNames.AppendElement(EmptyString());
  }
  sVolumeNameCache = new nsTArray<nsString>;
  sVolumeNameCache->AppendElements(aVolumeNames);
}

// static
void
nsDOMDeviceStorage::CreateDeviceStorageFor(nsPIDOMWindowInner* aWin,
                                           const nsAString &aType,
                                           nsDOMDeviceStorage** aStore)
{
  nsString storageName;
  GetDefaultStorageName(aType, storageName);

  RefPtr<nsDOMDeviceStorage> ds = new nsDOMDeviceStorage(aWin);
  if (NS_FAILED(ds->Init(aWin, aType, storageName))) {
    *aStore = nullptr;
    return;
  }
  ds.forget(aStore);
}

// static
void
nsDOMDeviceStorage::CreateDeviceStorageByNameAndType(
  nsPIDOMWindowInner* aWin,
  const nsAString& aName,
  const nsAString& aType,
  nsDOMDeviceStorage** aStore)
{
  if (!DeviceStorageTypeChecker::IsVolumeBased(aType)) {
    RefPtr<nsDOMDeviceStorage> storage = new nsDOMDeviceStorage(aWin);
    if (NS_FAILED(storage->Init(aWin, aType, EmptyString()))) {
      *aStore = nullptr;
      return;
    }
    NS_ADDREF(*aStore = storage.get());
    return;
  }

  RefPtr<nsDOMDeviceStorage> storage = GetStorageByNameAndType(aWin,
                                                                 aName,
                                                                 aType);
  if (!storage) {
    *aStore = nullptr;
    return;
  }
  NS_ADDREF(*aStore = storage.get());
}

bool
nsDOMDeviceStorage::Equals(nsPIDOMWindowInner* aWin,
                           const nsAString& aName,
                           const nsAString& aType)
{
  MOZ_ASSERT(aWin);

  return aWin && aWin->WindowID() == mInnerWindowID &&
         mStorageName.Equals(aName) &&
         mStorageType.Equals(aType);
}

// static
bool
nsDOMDeviceStorage::ParseFullPath(const nsAString& aFullPath,
                                  nsAString& aOutStorageName,
                                  nsAString& aOutStoragePath)
{
  aOutStorageName.Truncate();
  aOutStoragePath.Truncate();

  NS_NAMED_LITERAL_STRING(slash, "/");

  nsDependentSubstring storageName;

  if (StringBeginsWith(aFullPath, slash)) {
    int32_t slashIndex = aFullPath.FindChar('/', 1);
    if (slashIndex == kNotFound) {
      // names of the form /filename are illegal
      return false;
    }
    storageName.Rebind(aFullPath, 1, slashIndex - 1);
    aOutStoragePath = Substring(aFullPath, slashIndex + 1);
  } else {
    aOutStoragePath = aFullPath;
  }
  // If no volume name was specified in aFullPath, then aOutStorageName
  // will wind up being the empty string. It's up to the caller to figure
  // out which storage name to actually use.
  aOutStorageName = storageName;
  return true;
}

already_AddRefed<nsDOMDeviceStorage>
nsDOMDeviceStorage::GetStorage(const nsAString& aFullPath,
                               nsAString& aOutStoragePath)
{
  nsString storageName;
  if (!ParseFullPath(aFullPath, storageName, aOutStoragePath)) {
    return nullptr;
  }
  RefPtr<nsDOMDeviceStorage> ds;
  if (storageName.IsEmpty()) {
    ds = this;
  } else {
    ds = GetStorageByName(storageName);
  }
  return ds.forget();
}

already_AddRefed<nsDOMDeviceStorage>
nsDOMDeviceStorage::GetStorageByName(const nsAString& aStorageName)
{
  MOZ_ASSERT(IsOwningThread());

  RefPtr<nsDOMDeviceStorage> ds;

  if (mStorageName.Equals(aStorageName)) {
    ds = this;
    return ds.forget();
  }

  return GetStorageByNameAndType(GetOwner(), aStorageName, mStorageType);
}

// static
already_AddRefed<nsDOMDeviceStorage>
nsDOMDeviceStorage::GetStorageByNameAndType(nsPIDOMWindowInner* aWin,
                                            const nsAString& aStorageName,
                                            const nsAString& aType)
{
  MOZ_ASSERT(NS_IsMainThread());

  RefPtr<nsDOMDeviceStorage> ds;

  VolumeNameArray volNames;
  GetOrderedVolumeNames(volNames);
  VolumeNameArray::size_type numVolumes = volNames.Length();
  VolumeNameArray::index_type i;
  for (i = 0; i < numVolumes; i++) {
    if (volNames[i].Equals(aStorageName)) {
      ds = new nsDOMDeviceStorage(aWin);
      nsresult rv = ds->Init(aWin, aType, aStorageName);
      if (NS_FAILED(rv)) {
        return nullptr;
      }
      return ds.forget();
    }
  }
  return nullptr;
}

// static
void
nsDOMDeviceStorage::GetDefaultStorageName(const nsAString& aStorageType,
                                          nsAString& aStorageName)
{
  if (!DeviceStorageTypeChecker::IsVolumeBased(aStorageType)) {
    // The storage name will be the empty string
    aStorageName.Truncate();
    return;
  }

  // See if the preferred volume is available.
  nsString prefStorageName;
  DeviceStorageStatics::GetWritableName(prefStorageName);

  if (!prefStorageName.IsEmpty()) {
    nsString status;
    RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(aStorageType,
                                                            prefStorageName);
    dsf->GetStorageStatus(status);

    if (!status.EqualsLiteral("NoMedia")) {
      aStorageName = prefStorageName;
      return;
    }
  }

  // If there is no preferred storage or preferred storage is not presented,
  // we'll use the first one (which should be sdcard).
  VolumeNameArray volNames;
  GetOrderedVolumeNames(volNames);
  if (volNames.Length() > 0) {
    aStorageName = volNames[0];
    // overwrite the value of "device.storage.writable.name"
    DeviceStorageStatics::SetWritableName(aStorageName);
    return;
  }

  // No volumes available, return the empty string. This is normal for
  // b2g-desktop.
  aStorageName.Truncate();
}

bool
nsDOMDeviceStorage::IsAvailable()
{
  RefPtr<DeviceStorageFile> dsf(new DeviceStorageFile(mStorageType, mStorageName));
  return dsf->IsAvailable();
}

already_AddRefed<DOMRequest>
nsDOMDeviceStorage::Add(Blob* aBlob, ErrorResult& aRv)
{
  nsString path;
  return AddOrAppendNamed(aBlob, path, true, aRv);
}

already_AddRefed<DOMRequest>
nsDOMDeviceStorage::AddNamed(Blob* aBlob, const nsAString& aPath,
                             ErrorResult& aRv)
{
  if (aPath.IsEmpty()) {
    aRv.Throw(NS_ERROR_ILLEGAL_VALUE);
    return nullptr;
  }
  return AddOrAppendNamed(aBlob, aPath, true, aRv);
}

already_AddRefed<DOMRequest>
nsDOMDeviceStorage::AppendNamed(Blob* aBlob, const nsAString& aPath,
                                ErrorResult& aRv)
{
  if (aPath.IsEmpty()) {
    aRv.Throw(NS_ERROR_ILLEGAL_VALUE);
    return nullptr;
  }
  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::CreateAndRejectDOMRequest(const char *aReason, ErrorResult& aRv)
{
  RefPtr<DOMRequest> request;
  uint32_t id = CreateDOMRequest(getter_AddRefs(request), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  aRv = mManager->Reject(id, aReason);
  return request.forget();
}

already_AddRefed<DOMRequest>
nsDOMDeviceStorage::AddOrAppendNamed(Blob* aBlob, const nsAString& aPath,
                                     bool aCreate, ErrorResult& aRv)
{
  MOZ_ASSERT(IsOwningThread());
  MOZ_ASSERT(aCreate || !aPath.IsEmpty());

  // if the blob is null here, bail
  if (!aBlob) {
    return nullptr;
  }

  nsCOMPtr<nsIRunnable> r;

  if (IsFullPath(aPath)) {
    nsString storagePath;
    RefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
    if (!ds) {
      return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
    }

    return ds->AddOrAppendNamed(aBlob, storagePath, aCreate, aRv);
  }

  RefPtr<DOMRequest> domRequest;
  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  RefPtr<DeviceStorageFile> dsf;
  if (aPath.IsEmpty()) {
    dsf = new DeviceStorageFile(mStorageType, mStorageName);
  } else {
    dsf = new DeviceStorageFile(mStorageType, mStorageName, aPath);
    if (!dsf->IsSafePath()) {
      aRv = mManager->Reject(id, POST_ERROR_EVENT_PERMISSION_DENIED);
      return domRequest.forget();
    }
  }

  RefPtr<DeviceStorageRequest> request;
  if (aCreate) {
    request = new DeviceStorageCreateRequest();
  } else {
    request = new DeviceStorageAppendRequest();
  }
  request->Initialize(mManager, dsf.forget(), id, aBlob->Impl());
  aRv = CheckPermission(request.forget());
  return domRequest.forget();
}

already_AddRefed<DOMRequest>
nsDOMDeviceStorage::GetInternal(const nsAString& aPath, bool aEditable,
                                ErrorResult& aRv)
{
  MOZ_ASSERT(IsOwningThread());

  if (IsFullPath(aPath)) {
    nsString storagePath;
    RefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
    if (!ds) {
      return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
    }
    return ds->GetInternal(storagePath, aEditable, aRv);
  }

  RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                          mStorageName,
                                                          aPath);
  dsf->SetEditable(aEditable);
  if (!dsf->IsSafePath()) {
    return CreateAndRejectDOMRequest(POST_ERROR_EVENT_PERMISSION_DENIED, aRv);
  }

  RefPtr<DOMRequest> domRequest;
  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  RefPtr<DeviceStorageRequest> request = new DeviceStorageOpenRequest();
  request->Initialize(mManager, dsf.forget(), id);

  aRv = CheckPermission(request.forget());
  return domRequest.forget();
}

already_AddRefed<DOMRequest>
nsDOMDeviceStorage::Delete(const nsAString& aPath, ErrorResult& aRv)
{
  MOZ_ASSERT(IsOwningThread());

  if (IsFullPath(aPath)) {
    nsString storagePath;
    RefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
    if (!ds) {
      return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
    }
    return ds->Delete(storagePath, aRv);
  }

  RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                          mStorageName,
                                                          aPath);
  if (!dsf->IsSafePath()) {
    return CreateAndRejectDOMRequest(POST_ERROR_EVENT_PERMISSION_DENIED, aRv);
  }

  RefPtr<DOMRequest> domRequest;
  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  RefPtr<DeviceStorageRequest> request = new DeviceStorageDeleteRequest();
  request->Initialize(mManager, dsf.forget(), id);

  aRv = CheckPermission(request.forget());
  return domRequest.forget();
}

already_AddRefed<DOMRequest>
nsDOMDeviceStorage::FreeSpace(ErrorResult& aRv)
{
  MOZ_ASSERT(IsOwningThread());

  RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                          mStorageName);

  RefPtr<DOMRequest> domRequest;
  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  RefPtr<DeviceStorageRequest> request = new DeviceStorageFreeSpaceRequest();
  request->Initialize(mManager, dsf.forget(), id);

  aRv = CheckPermission(request.forget());
  return domRequest.forget();
}

already_AddRefed<DOMRequest>
nsDOMDeviceStorage::UsedSpace(ErrorResult& aRv)
{
  MOZ_ASSERT(IsOwningThread());

  DebugOnly<DeviceStorageUsedSpaceCache*> usedSpaceCache
    = DeviceStorageUsedSpaceCache::CreateOrGet();
  MOZ_ASSERT(usedSpaceCache);

  RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                          mStorageName);

  RefPtr<DOMRequest> domRequest;
  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  RefPtr<DeviceStorageRequest> request = new DeviceStorageUsedSpaceRequest();
  request->Initialize(mManager, dsf.forget(), id);

  aRv = CheckPermission(request.forget());
  return domRequest.forget();
}

already_AddRefed<DOMRequest>
nsDOMDeviceStorage::Available(ErrorResult& aRv)
{
  MOZ_ASSERT(IsOwningThread());

  RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                          mStorageName);

  RefPtr<DOMRequest> domRequest;
  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  RefPtr<DeviceStorageRequest> request = new DeviceStorageAvailableRequest();
  request->Initialize(mManager, dsf.forget(), id);

  aRv = CheckPermission(request.forget());
  return domRequest.forget();
}

already_AddRefed<DOMRequest>
nsDOMDeviceStorage::StorageStatus(ErrorResult& aRv)
{
  MOZ_ASSERT(IsOwningThread());

  RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                          mStorageName);

  RefPtr<DOMRequest> domRequest;
  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  RefPtr<DeviceStorageRequest> request = new DeviceStorageStatusRequest();
  request->Initialize(mManager, dsf.forget(), id);

  aRv = CheckPermission(request.forget());
  return domRequest.forget();
}

already_AddRefed<DOMRequest>
nsDOMDeviceStorage::Format(ErrorResult& aRv)
{
  MOZ_ASSERT(IsOwningThread());

  RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                          mStorageName);

  RefPtr<DOMRequest> domRequest;
  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  RefPtr<DeviceStorageRequest> request = new DeviceStorageFormatRequest();
  request->Initialize(mManager, dsf.forget(), id);

  aRv = CheckPermission(request.forget());
  return domRequest.forget();
}

already_AddRefed<DOMRequest>
nsDOMDeviceStorage::Mount(ErrorResult& aRv)
{
  MOZ_ASSERT(IsOwningThread());

  RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                          mStorageName);

  RefPtr<DOMRequest> domRequest;
  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  RefPtr<DeviceStorageRequest> request = new DeviceStorageMountRequest();
  request->Initialize(mManager, dsf.forget(), id);

  aRv = CheckPermission(request.forget());
  return domRequest.forget();
}

already_AddRefed<DOMRequest>
nsDOMDeviceStorage::Unmount(ErrorResult& aRv)
{
  MOZ_ASSERT(IsOwningThread());

  RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                          mStorageName);

  RefPtr<DOMRequest> domRequest;
  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }


  RefPtr<DeviceStorageRequest> request = new DeviceStorageUnmountRequest();
  request->Initialize(mManager, dsf.forget(), id);

  aRv = CheckPermission(request.forget());
  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;
    RefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
    if (!ds) {
      return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
    }
    return ds->CreateFileDescriptor(storagePath, aDSFileDescriptor, aRv);
  }

  RefPtr<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);
  }

  RefPtr<DOMRequest> domRequest;
  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  RefPtr<DeviceStorageRequest> request = new DeviceStorageCreateFdRequest();
  request->Initialize(mManager, dsf.forget(), id, aDSFileDescriptor);

  aRv = CheckPermission(request.forget());
  return domRequest.forget();
}

bool
nsDOMDeviceStorage::Default()
{
  nsString defaultStorageName;
  GetDefaultStorageName(mStorageType, defaultStorageName);
  return mStorageName.Equals(defaultStorageName);
}

bool
nsDOMDeviceStorage::CanBeFormatted()
{
  // Currently, any volume which can be shared can also be formatted.
  return mIsShareable;
}

bool
nsDOMDeviceStorage::CanBeMounted()
{
  // Currently, any volume which can be shared can also be mounted/unmounted.
  return mIsShareable;
}

bool
nsDOMDeviceStorage::CanBeShared()
{
  return mIsShareable;
}

bool
nsDOMDeviceStorage::IsRemovable()
{
  return mIsRemovable;
}

bool
nsDOMDeviceStorage::LowDiskSpace()
{
  return DeviceStorageStatics::LowDiskSpace();
}

already_AddRefed<Promise>
nsDOMDeviceStorage::GetRoot(ErrorResult& aRv)
{
  if (!mFileSystem) {
    mFileSystem = new DeviceStorageFileSystem(mStorageType, mStorageName);
    mFileSystem->Init(this);
  }
  return mozilla::dom::Directory::GetRoot(mFileSystem, aRv);
}

void
nsDOMDeviceStorage::GetStorageName(nsAString& aStorageName)
{
  aStorageName = mStorageName;
}

already_AddRefed<DOMCursor>
nsDOMDeviceStorage::Enumerate(const nsAString& aPath,
                              const EnumerationParameters& aOptions,
                              ErrorResult& aRv)
{
  return EnumerateInternal(aPath, aOptions, false, aRv);
}

already_AddRefed<DOMCursor>
nsDOMDeviceStorage::EnumerateEditable(const nsAString& aPath,
                                      const EnumerationParameters& aOptions,
                                      ErrorResult& aRv)
{
  return EnumerateInternal(aPath, aOptions, true, aRv);
}


already_AddRefed<DOMCursor>
nsDOMDeviceStorage::EnumerateInternal(const nsAString& aPath,
                                      const EnumerationParameters& aOptions,
                                      bool aEditable, ErrorResult& aRv)
{
  MOZ_ASSERT(IsOwningThread());

  PRTime since = 0;
  if (aOptions.mSince.WasPassed() && !aOptions.mSince.Value().IsUndefined()) {
    since = PRTime(aOptions.mSince.Value().TimeStamp().toDouble());
  }

  RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                          mStorageName,
                                                          aPath,
                                                          EmptyString());
  dsf->SetEditable(aEditable);

  RefPtr<DeviceStorageCursorRequest> request = new DeviceStorageCursorRequest();
  RefPtr<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.forget(), id, since);
    aRv = CheckPermission(request.forget());
  }

  return cursor.forget();
}

void
nsDOMDeviceStorage::OnWritableNameChanged()
{
  nsAdoptingString DefaultLocation;
  GetDefaultStorageName(mStorageType, DefaultLocation);

  DeviceStorageChangeEventInit init;
  init.mBubbles = true;
  init.mCancelable = false;
  init.mPath = DefaultLocation;

  if (mIsDefaultLocation) {
    init.mReason.AssignLiteral("default-location-changed");
  } else {
    init.mReason.AssignLiteral("became-default-location");
  }

  RefPtr<DeviceStorageChangeEvent> event =
    DeviceStorageChangeEvent::Constructor(this,
                                          NS_LITERAL_STRING(STORAGE_CHANGE_EVENT),
                                          init);
  event->SetTrusted(true);

  bool ignore;
  DispatchEvent(event, &ignore);
  mIsDefaultLocation = Default();
}

#ifdef MOZ_WIDGET_GONK
void
nsDOMDeviceStorage::DispatchStatusChangeEvent(nsAString& aStatus)
{
  if (aStatus == mLastStatus) {
    // We've already sent this status, don't bother sending it again.
    return;
  }
  mLastStatus = aStatus;

  DeviceStorageChangeEventInit init;
  init.mBubbles = true;
  init.mCancelable = false;
  init.mPath = mStorageName;
  init.mReason = aStatus;

  RefPtr<DeviceStorageChangeEvent> event =
    DeviceStorageChangeEvent::Constructor(this,
                                          NS_LITERAL_STRING(STORAGE_CHANGE_EVENT),
                                          init);
  event->SetTrusted(true);

  bool ignore;
  DispatchEvent(event, &ignore);
}

void
nsDOMDeviceStorage::DispatchStorageStatusChangeEvent(nsAString& aStorageStatus)
{
  if (aStorageStatus == mLastStorageStatus) {
     // We've already sent this status, don't bother sending it again.
    return;
  }
  mLastStorageStatus = aStorageStatus;

  DeviceStorageChangeEventInit init;
  init.mBubbles = true;
  init.mCancelable = false;
  init.mPath = mStorageName;
  init.mReason = aStorageStatus;

  RefPtr<DeviceStorageChangeEvent> event =
    DeviceStorageChangeEvent::Constructor(this, NS_LITERAL_STRING("storage-state-change"),
                                          init);
  event->SetTrusted(true);

  bool ignore;
  DispatchEvent(event, &ignore);
}
#endif

void
nsDOMDeviceStorage::OnFileWatcherUpdate(const nsCString& aData, DeviceStorageFile* aFile)
{
  MOZ_ASSERT(IsOwningThread());
  Notify(aData.get(), aFile);
}

void
nsDOMDeviceStorage::OnDiskSpaceWatcher(bool aLowDiskSpace)
{
  MOZ_ASSERT(IsOwningThread());
  RefPtr<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(IsOwningThread());

  // We invalidate the used space cache for the volume that actually changed
  // state.
  nsString volName;
  aVolume->GetName(volName);

  DeviceStorageUsedSpaceCache* usedSpaceCache
    = DeviceStorageUsedSpaceCache::CreateOrGet();
  MOZ_ASSERT(usedSpaceCache);
  usedSpaceCache->Invalidate(volName);

  if (!volName.Equals(mStorageName)) {
    // Not our volume - we can ignore.
    return;
  }

  RefPtr<DeviceStorageFile> dsf(new DeviceStorageFile(mStorageType, mStorageName));
  nsString status, storageStatus;

  // Get Status (one of "available, unavailable, shared")
  dsf->GetStatus(status);
  DispatchStatusChangeEvent(status);

  // Get real volume status (defined in dom/system/gonk/nsIVolume.idl)
  dsf->GetStorageStatus(storageStatus);
  DispatchStorageStatusChangeEvent(storageStatus);
}
#endif

nsresult
nsDOMDeviceStorage::Notify(const char* aReason, DeviceStorageFile* aFile)
{
  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;
  }

  DeviceStorageChangeEventInit init;
  init.mBubbles = true;
  init.mCancelable = false;
  aFile->GetFullPath(init.mPath);
  init.mReason.AssignWithConversion(aReason);

  RefPtr<DeviceStorageChangeEvent> event =
    DeviceStorageChangeEvent::Constructor(this,
                                          NS_LITERAL_STRING(STORAGE_CHANGE_EVENT),
                                          init);
  event->SetTrusted(true);

  bool ignore;
  DispatchEvent(event, &ignore);
  return NS_OK;
}

void
nsDOMDeviceStorage::EventListenerWasAdded(const nsAString& aType,
                                          ErrorResult& aRv,
                                          JSCompartment* aCompartment)
{
  MOZ_ASSERT(IsOwningThread());

  if (!mManager) {
    return;
  }

  if (mManager->CheckPermission(DEVICE_STORAGE_ACCESS_READ) !=
      nsIPermissionManager::PROMPT_ACTION) {
    return;
  }

  if (!aType.EqualsLiteral(STORAGE_CHANGE_EVENT)) {
    return;
  }

  RefPtr<DOMRequest> domRequest;
  uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
  if (aRv.Failed()) {
    return;
  }

  RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                          mStorageName);
  RefPtr<DeviceStorageRequest> request = new DeviceStorageWatchRequest();
  request->Initialize(mManager, dsf.forget(), id);
  aRv = CheckPermission(request.forget());
}

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, mPending[i].mRequest.forget());
    }
  }
}

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(
  already_AddRefed<nsIRunnable>&& aRunnable)
{
  return mOwningThread->Dispatch(Move(aRunnable), NS_DISPATCH_NORMAL);
}

nsresult
DeviceStorageRequestManager::DispatchOrAbandon(
  uint32_t aId, already_AddRefed<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);
    nsCOMPtr<nsIRunnable> runnable(aRunnable);
    return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
  }

  nsresult rv = DispatchToOwningThread(Move(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);

  RefPtr<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);

  RefPtr<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);
    RefPtr<DeviceStorageRequestManager> self = this;
    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self, aId] () -> void
    {
      self->Resolve(aId, false);
    });
    return DispatchOrAbandon(aId, r.forget());
  }

  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);
    RefPtr<DeviceStorageRequestManager> self = this;
    nsString result = aResult;
    nsCOMPtr<nsIRunnable> r
      = NS_NewRunnableFunction([self, aId, result] () -> void
    {
      self->Resolve(aId, result, false);
    });
    return DispatchOrAbandon(aId, r.forget());
  }

  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);
    RefPtr<DeviceStorageRequestManager> self = this;
    nsCOMPtr<nsIRunnable> r
      = NS_NewRunnableFunction([self, aId, aValue] () -> void
    {
      self->Resolve(aId, aValue, false);
    });
    return DispatchOrAbandon(aId, r.forget());
  }

  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;
  }

  JS::RootedValue value(nsContentUtils::RootingCxForThread(),
                        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);

  RefPtr<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);
    RefPtr<DeviceStorageRequestManager> self = this;
    RefPtr<BlobImpl> blobImpl = aBlobImpl;
    nsCOMPtr<nsIRunnable> r
      = NS_NewRunnableFunction([self, aId, blobImpl] () -> void
    {
      self->Resolve(aId, blobImpl, false);
    });
    return DispatchOrAbandon(aId, r.forget());
  }

  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));
  }

  RefPtr<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. */
  RefPtr<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. */
  RefPtr<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;
  }

  RefPtr<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.forget());
}

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();
}