Bug 858416 - Create a composite device storage interface. r=dougt
authorDave Hylands <dhylands@mozilla.com>
Sat, 11 May 2013 02:10:18 -0700
changeset 138445 ba7496b49c06b6a670a62a7c2d275eee52d38f1b
parent 138444 491005c97e8041ee268b3ca203d6e74b1496e4b4
child 138446 044d554846ff020bebc1503a5e00fdd6c21fd97e
push idunknown
push userunknown
push dateunknown
reviewersdougt
bugs858416
milestone23.0a1
Bug 858416 - Create a composite device storage interface. r=dougt * * * Bug 858416 - Fix try test failures
b2g/chrome/content/settings.js
dom/base/Navigator.cpp
dom/bluetooth/BluetoothOppManager.cpp
dom/bluetooth/BluetoothOppManager.h
dom/camera/GonkCameraControl.cpp
dom/devicestorage/DeviceStorage.h
dom/devicestorage/DeviceStorageRequestChild.cpp
dom/devicestorage/DeviceStorageRequestParent.cpp
dom/devicestorage/PDeviceStorageRequest.ipdl
dom/devicestorage/nsDeviceStorage.cpp
dom/devicestorage/nsDeviceStorage.h
dom/devicestorage/test/test_823965.html
dom/devicestorage/test/test_enumerate.html
dom/devicestorage/test/test_enumerateNoParam.html
dom/devicestorage/test/test_lastModificationFilter.html
dom/devicestorage/test/test_watch.html
dom/devicestorage/test/test_watchOther.html
dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/system/gonk/nsVolumeService.cpp
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -256,16 +256,21 @@ SettingsListener.observe('devtools.debug
   }
 #endif
 });
 
 SettingsListener.observe('debug.log-animations.enabled', false, function(value) {
   Services.prefs.setBoolPref('layers.offmainthreadcomposition.log-animations', value);
 });
 
+// =================== Device Storage ====================
+SettingsListener.observe('device.storage.writable.name', false, function(value) {
+  Services.prefs.setBoolPref('device.storage.writable.name', value);
+});
+
 // =================== Privacy ====================
 SettingsListener.observe('privacy.donottrackheader.enabled', false, function(value) {
   Services.prefs.setBoolPref('privacy.donottrackheader.enabled', value);
 });
 
 // =================== Crash Reporting ====================
 SettingsListener.observe('app.reportCrashes', 'ask', function(value) {
   if (value == 'always') {
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -948,48 +948,31 @@ NS_IMETHODIMP Navigator::GetDeviceStorag
 {
   NS_ENSURE_ARG_POINTER(_retval);
   *_retval = nullptr;
 
   if (!Preferences::GetBool("device.storage.enabled", false)) {
     return NS_OK;
   }
 
-  // We're going to obsolete getDeviceStorage, but want to leave it in for
-  // compatability right now. So we do essentially the same thing as GetDeviceStorages
-  // but only take the first element of the array.
-
-  NS_WARNING("navigator.getDeviceStorage is deprecated. Returning navigator.getDeviceStorages[0]");
-
-  nsCOMPtr<nsIVariant> variantArray;
+  nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mWindow));
 
-  nsresult rv = GetDeviceStorages(aType, getter_AddRefs(variantArray));
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (!win || !win->GetOuterWindow() || !win->GetDocShell()) {
+    return NS_ERROR_FAILURE;
+  }
 
-  uint16_t dataType;
-  variantArray->GetDataType(&dataType);
+  nsRefPtr<nsDOMDeviceStorage> storage;
+  nsDOMDeviceStorage::CreateDeviceStorageFor(win, aType, getter_AddRefs(storage));
 
-  if (dataType != nsIDataType::VTYPE_ARRAY) {
-    NS_ASSERTION(dataType == nsIDataType::VTYPE_EMPTY_ARRAY,
-                 "Expecting an empty array");
+  if (!storage) {
     return NS_OK;
   }
 
-  uint16_t valueType;
-  nsIID iid;
-  uint32_t valueCount;
-  void* rawArray;
-  variantArray->GetAsArray(&valueType, &iid, &valueCount, &rawArray);
-  NS_ASSERTION(valueCount > 0, "Expecting non-zero array size");
-  nsIDOMDeviceStorage** values = static_cast<nsIDOMDeviceStorage**>(rawArray);
-  *_retval = values[0];
-  for (uint32_t i = 1; i < valueCount; i++) {
-    values[i]->Release();
-  }
-  nsMemory::Free(rawArray);
+  NS_ADDREF(*_retval = storage.get());
+  mDeviceStorageStores.AppendElement(storage);
   return NS_OK;
 }
 
 NS_IMETHODIMP Navigator::GetDeviceStorages(const nsAString &aType, nsIVariant** _retval)
 {
   NS_ENSURE_ARG_POINTER(_retval);
   *_retval = nullptr;
 
--- a/dom/bluetooth/BluetoothOppManager.cpp
+++ b/dom/bluetooth/BluetoothOppManager.cpp
@@ -24,19 +24,17 @@
 #include "nsIObserverService.h"
 #include "nsIDOMFile.h"
 #include "nsIFile.h"
 #include "nsIInputStream.h"
 #include "nsIMIMEService.h"
 #include "nsIOutputStream.h"
 #include "nsNetUtil.h"
 
-#define TARGET_ROOT   "/sdcard/"
 #define TARGET_SUBDIR "downloads/bluetooth/"
-#define TARGET_FOLDER TARGET_ROOT TARGET_SUBDIR
 
 USING_BLUETOOTH_NAMESPACE
 using namespace mozilla;
 using namespace mozilla::ipc;
 
 class BluetoothOppManagerObserver : public nsIObserver
 {
 public:
@@ -480,97 +478,51 @@ BluetoothOppManager::AfterOppDisconnecte
     mReadFileThread->Shutdown();
     mReadFileThread = nullptr;
   }
 }
 
 void
 BluetoothOppManager::DeleteReceivedFile()
 {
-  nsString path;
-  path.AssignLiteral(TARGET_FOLDER);
-
-  nsCOMPtr<nsIFile> f;
-  nsresult rv = NS_NewLocalFile(path + sFileName, false, getter_AddRefs(f));
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Couldn't find received file, nothing to delete.");
-    return;
-  }
-
   if (mOutputStream) {
     mOutputStream->Close();
     mOutputStream = nullptr;
   }
 
-  f->Remove(false);
-}
-
-DeviceStorageFile*
-BluetoothOppManager::CreateDeviceStorageFile(nsIFile* aFile)
-{
-  nsString fullFilePath;
-  aFile->GetPath(fullFilePath);
-
-  MOZ_ASSERT(StringBeginsWith(fullFilePath, NS_LITERAL_STRING(TARGET_ROOT)));
-
-  nsDependentSubstring storagePath =
-    Substring(fullFilePath, strlen(TARGET_ROOT));
-
-  nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
-  NS_ENSURE_TRUE(mimeSvc, nullptr);
-
-  nsCString mimeType;
-  nsresult rv = mimeSvc->GetTypeFromFile(aFile, mimeType);
-  if (NS_FAILED(rv)) {
-    return nullptr;
-  }
-
-  if (StringBeginsWith(mimeType, NS_LITERAL_CSTRING("image/"))) {
-    return new DeviceStorageFile(NS_LITERAL_STRING("pictures"), storagePath);
-  } else if (StringBeginsWith(mimeType, NS_LITERAL_CSTRING("video/"))) {
-    return new DeviceStorageFile(NS_LITERAL_STRING("videos"), storagePath);
-  } else if (StringBeginsWith(mimeType, NS_LITERAL_CSTRING("audio/"))) {
-    return new DeviceStorageFile(NS_LITERAL_STRING("music"), storagePath);
-  } else {
-    NS_WARNING("Couldn't recognize the mimetype of received file.");
-    return nullptr;
+  if (mDsFile && mDsFile->mFile) {
+    mDsFile->mFile->Remove(false);
   }
 }
 
 bool
 BluetoothOppManager::CreateFile()
 {
   MOZ_ASSERT(mPacketLeftLength == 0);
 
   nsString path;
-  path.AssignLiteral(TARGET_FOLDER);
+  path.AssignLiteral(TARGET_SUBDIR);
+  path.Append(sFileName);
 
-  nsCOMPtr<nsIFile> f;
-  nsresult rv;
-  rv = NS_NewLocalFile(path + sFileName, false, getter_AddRefs(f));
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Couldn't new a local file");
-    return false;
-  }
-
-  rv = f->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00644);
-  if (NS_FAILED(rv)) {
+  mDsFile = DeviceStorageFile::CreateUnique(path, nsIFile::NORMAL_FILE_TYPE, 0644);
+  if (!mDsFile) {
     NS_WARNING("Couldn't create the file");
     return false;
   }
 
+  nsCOMPtr<nsIFile> f;
+  mDsFile->mFile->Clone(getter_AddRefs(f));
+  
   /*
    * The function CreateUnique() may create a file with a different file
    * name from the original sFileName. Therefore we have to retrieve
    * the file name again.
    */
   f->GetLeafName(sFileName);
 
-  mDsFile = CreateDeviceStorageFile(f);
-
   NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), f);
   NS_ENSURE_TRUE(mOutputStream, false);
 
   return true;
 }
 
 bool
 BluetoothOppManager::WriteToFile(const uint8_t* aData, int aDataLength)
--- a/dom/bluetooth/BluetoothOppManager.h
+++ b/dom/bluetooth/BluetoothOppManager.h
@@ -104,17 +104,16 @@ private:
   void ReplyToPut(bool aFinal, bool aContinue);
   void AfterOppConnected();
   void AfterFirstPut();
   void AfterOppDisconnected();
   void ValidateFileName();
   bool IsReservedChar(PRUnichar c);
   void ClearQueue();
   void RetrieveSentFileName();
-  DeviceStorageFile* CreateDeviceStorageFile(nsIFile* aFile);
   void NotifyAboutFileChange();
 
   /**
    * OBEX session status.
    * Set when OBEX session is established.
    */
   bool mConnected;
   nsString mConnectedDeviceAddress;
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -29,16 +29,18 @@
 #include "nsThread.h"
 #include <media/MediaProfiles.h>
 #include "mozilla/FileUtils.h"
 #include "mozilla/Services.h"
 #include "nsAlgorithm.h"
 #include <media/mediaplayer.h>
 #include "nsPrintfCString.h"
 #include "nsIObserverService.h"
+#include "nsIVolume.h"
+#include "nsIVolumeService.h"
 #include "DOMCameraManager.h"
 #include "GonkCameraHwMgr.h"
 #include "DOMCameraCapabilities.h"
 #include "DOMCameraControl.h"
 #include "GonkRecorderProfiles.h"
 #include "GonkCameraControl.h"
 #include "CameraCommon.h"
 
@@ -900,17 +902,32 @@ nsGonkCameraControl::StartRecordingImpl(
    * filename to it.  The filename may include a relative subpath
    * (e.g.) "DCIM/IMG_0001.jpg".
    *
    * The camera app needs to provide the file extension '.3gp' for now.
    * See bug 795202.
    */
   nsCOMPtr<nsIFile> filename = aStartRecording->mFolder;
   filename->AppendRelativePath(aStartRecording->mFilename);
+
+  nsString fullpath;
+  filename->GetPath(fullpath);
+
+  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(vs, NS_ERROR_FAILURE);
+
+  nsCOMPtr<nsIVolume> vol;
+  nsresult rv = vs->GetVolumeByPath(fullpath, getter_AddRefs(vol));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
+
+  nsString volName;
+  vol->GetName(volName);
+
   mVideoFile = new DeviceStorageFile(NS_LITERAL_STRING("videos"),
+                                     volName,
                                      aStartRecording->mFilename);
 
   nsAutoCString nativeFilename;
   filename->GetNativePath(nativeFilename);
   DOM_CAMERA_LOGI("Video filename is '%s'\n", nativeFilename.get());
 
   if (!mVideoFile->IsSafePath()) {
     DOM_CAMERA_LOGE("Invalid video file name\n");
@@ -918,17 +935,17 @@ nsGonkCameraControl::StartRecordingImpl(
   }
 
   ScopedClose fd(open(nativeFilename.get(), O_RDWR | O_CREAT, 0644));
   if (fd < 0) {
     DOM_CAMERA_LOGE("Couldn't create file '%s': (%d) %s\n", nativeFilename.get(), errno, strerror(errno));
     return NS_ERROR_FAILURE;
   }
 
-  nsresult rv = SetupRecording(fd, aStartRecording->mOptions.rotation, aStartRecording->mOptions.maxFileSizeBytes, aStartRecording->mOptions.maxVideoLengthMs);
+  rv = SetupRecording(fd, aStartRecording->mOptions.rotation, aStartRecording->mOptions.maxFileSizeBytes, aStartRecording->mOptions.maxVideoLengthMs);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mRecorder->start() != OK) {
     DOM_CAMERA_LOGE("mRecorder->start() failed\n");
     // important: we MUST destroy the recorder if start() fails!
     mRecorder = nullptr;
     return NS_ERROR_FAILURE;
   }
--- a/dom/devicestorage/DeviceStorage.h
+++ b/dom/devicestorage/DeviceStorage.h
@@ -7,71 +7,97 @@
 #ifndef DeviceStorage_h
 #define DeviceStorage_h
 
 #include "nsIDOMDeviceStorage.h"
 #include "nsIFile.h"
 #include "nsIPrincipal.h"
 #include "nsIObserver.h"
 #include "nsDOMEventTargetHelper.h"
+#include "mozilla/RefPtr.h"
 #include "mozilla/StaticPtr.h"
+#include "DOMRequest.h"
 
 #define DEVICESTORAGE_PICTURES   "pictures"
 #define DEVICESTORAGE_VIDEOS     "videos"
 #define DEVICESTORAGE_MUSIC      "music"
 #define DEVICESTORAGE_APPS       "apps"
 #define DEVICESTORAGE_SDCARD     "sdcard"
 
 class DeviceStorageFile MOZ_FINAL
   : public nsISupports {
 public:
   nsCOMPtr<nsIFile> mFile;
+  nsString mStorageType;
+  nsString mStorageName;
+  nsString mRootDir;
   nsString mPath;
-  nsString mStorageType;
-  nsString mRootDir;
   bool mEditable;
 
   // Used when the path will be set later via SetPath.
-  DeviceStorageFile(const nsAString& aStorageType);
+  DeviceStorageFile(const nsAString& aStorageType,
+                    const nsAString& aStorageName);
   // Used for non-enumeration purposes.
-  DeviceStorageFile(const nsAString& aStorageType, const nsAString& aPath);
+  DeviceStorageFile(const nsAString& aStorageType,
+                    const nsAString& aStorageName,
+                    const nsAString& aPath);
   // Used for enumerations. When you call Enumerate, you can pass in a directory to enumerate
   // and the results that are returned are relative to that directory, files related to an
   // enumeration need to know the "root of the enumeration" directory.
-  DeviceStorageFile(const nsAString& aStorageType, const nsAString& aRootDir, const nsAString& aPath);
+  DeviceStorageFile(const nsAString& aStorageType,
+                    const nsAString& aStorageName,
+                    const nsAString& aRootDir,
+                    const nsAString& aPath);
 
   void SetPath(const nsAString& aPath);
   void SetEditable(bool aEditable);
 
+  static already_AddRefed<DeviceStorageFile> CreateUnique(nsAString& aFileName,
+                                                          uint32_t aFileType,
+                                                          uint32_t aFileAttributes);
+
   NS_DECL_ISUPPORTS
 
+  bool IsAvailable();
+  bool IsComposite();
+  void GetCompositePath(nsAString& aCompositePath);
+
   // we want to make sure that the names of file can't reach
   // outside of the type of storage the user asked for.
   bool IsSafePath();
   bool IsSafePath(const nsAString& aPath);
 
+  void Dump(const char* label);
+
   nsresult Remove();
   nsresult Write(nsIInputStream* aInputStream);
   nsresult Write(InfallibleTArray<uint8_t>& bits);
-  void CollectFiles(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles, PRTime aSince = 0);
-  void collectFilesInternal(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles, PRTime aSince, nsAString& aRootPath);
+  void CollectFiles(nsTArray<nsRefPtr<DeviceStorageFile> >& aFiles,
+                    PRTime aSince = 0);
+  void collectFilesInternal(nsTArray<nsRefPtr<DeviceStorageFile> >& aFiles,
+                            PRTime aSince, nsAString& aRootPath);
 
-  static void DirectoryDiskUsage(nsIFile* aFile,
-                                 uint64_t* aPicturesSoFar,
-                                 uint64_t* aVideosSoFar,
-                                 uint64_t* aMusicSoFar,
-                                 uint64_t* aTotalSoFar);
+  void AccumDiskUsage(uint64_t* aPicturesSoFar, uint64_t* aVideosSoFar,
+                      uint64_t* aMusicSoFar, uint64_t* aTotalSoFar);
 
-  static void GetRootDirectoryForType(const nsAString& aType,
-                                      const nsAString& aVolName,
+  void GetDiskFreeSpace(int64_t* aSoFar);
+  void GetStatus(nsAString& aStatus);
+  static void GetRootDirectoryForType(const nsAString& aStorageType,
+                                      const nsAString& aStorageName,
                                       nsIFile** aFile);
 private:
-  void Init(const nsAString& aStorageType);
+  void Init();
   void NormalizeFilePath();
   void AppendRelativePath(const nsAString& aPath);
+  void GetStatusInternal(nsAString& aStorageName, nsAString& aStatus);
+  void AccumDirectoryUsage(nsIFile* aFile,
+                           uint64_t* aPicturesSoFar,
+                           uint64_t* aVideosSoFar,
+                           uint64_t* aMusicSoFar,
+                           uint64_t* aTotalSoFar);
 };
 
 /*
   The FileUpdateDispatcher converts file-watcher-notify
   observer events to file-watcher-update events.  This is
   used to be able to broadcast events from one child to
   another child in B2G.  (f.e., if one child decides to add
   a file, we want to be able to able to send a onchange
@@ -95,16 +121,18 @@ class FileUpdateDispatcher MOZ_FINAL
 };
 
 class nsDOMDeviceStorage MOZ_FINAL
   : public nsDOMEventTargetHelper
   , public nsIDOMDeviceStorage
   , public nsIObserver
 {
 public:
+  typedef nsTArray<nsString> VolumeNameArray;
+
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIDOMDEVICESTORAGE
 
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIDOMEVENTTARGET
   virtual void AddEventListener(const nsAString& aType,
                                 nsIDOMEventListener* aListener,
                                 bool aUseCapture,
@@ -112,59 +140,96 @@ public:
                                 mozilla::ErrorResult& aRv) MOZ_OVERRIDE;
   virtual void RemoveEventListener(const nsAString& aType,
                                    nsIDOMEventListener* aListener,
                                    bool aUseCapture,
                                    mozilla::ErrorResult& aRv) MOZ_OVERRIDE;
 
   nsDOMDeviceStorage();
 
-  nsresult Init(nsPIDOMWindow* aWindow, const nsAString &aType, const nsAString &aVolName);
+  nsresult Init(nsPIDOMWindow* aWindow, const nsAString& aType,
+                nsTArray<nsRefPtr<nsDOMDeviceStorage> >& aStores);
+  nsresult Init(nsPIDOMWindow* aWindow, const nsAString& aType,
+                const nsAString& aVolName);
 
-  void SetRootDirectoryForType(const nsAString& aType, const nsAString &aVolName);
+  bool IsAvailable();
+
+  void SetRootDirectoryForType(const nsAString& aType, const nsAString& aVolName);
 
-  static void GetOrderedVolumeNames(const nsAString &aType,
-                                    nsTArray<nsString> &aVolumeNames);
+  static void CreateDeviceStorageFor(nsPIDOMWindow* aWin,
+                                     const nsAString& aType,
+                                     nsDOMDeviceStorage** aStore);
 
   static void CreateDeviceStoragesFor(nsPIDOMWindow* aWin,
-                                      const nsAString &aType,
-                                      nsTArray<nsRefPtr<nsDOMDeviceStorage> > &aStores);
+                                      const nsAString& aType,
+                                      nsTArray<nsRefPtr<nsDOMDeviceStorage> >& aStores);
   void Shutdown();
 
+  static void GetOrderedVolumeNames(nsTArray<nsString>& aVolumeNames);
+
+  static void GetWritableStorageName(const nsAString& aStorageType,
+                                     nsAString &aStorageName);
+
+  static bool ParseCompositePath(const nsAString& aCompositePath,
+                                 nsAString& aOutStorageName,
+                                 nsAString& aOutStoragePath);
 private:
   ~nsDOMDeviceStorage();
 
   nsresult GetInternal(const JS::Value & aName,
                        JSContext* aCx,
                        nsIDOMDOMRequest** aRetval,
                        bool aEditable);
 
-  nsresult EnumerateInternal(const JS::Value & aName,
-                             const JS::Value & aOptions,
+  nsresult GetInternal(nsPIDOMWindow* aWin,
+                       const nsAString& aPath,
+                       mozilla::dom::DOMRequest* aRequest,
+                       bool aEditable);
+
+  nsresult DeleteInternal(nsPIDOMWindow* aWin,
+                          const nsAString& aPath,
+                          mozilla::dom::DOMRequest* aRequest);
+
+  nsresult EnumerateInternal(const JS::Value& aName,
+                             const JS::Value& aOptions,
                              JSContext* aCx,
                              uint8_t aArgc,
                              bool aEditable,
                              nsIDOMDOMCursor** aRetval);
 
   nsString mStorageType;
   nsCOMPtr<nsIFile> mRootDirectory;
-  nsString mVolumeName;
+  nsString mStorageName;
+
+  bool IsComposite() { return mStores.Length() > 0; }
+  nsTArray<nsRefPtr<nsDOMDeviceStorage> > mStores;
+  already_AddRefed<nsDOMDeviceStorage> GetStorage(const nsAString& aCompositePath,
+                                                  nsAString& aOutStoragePath);
+  already_AddRefed<nsDOMDeviceStorage> GetStorageByName(const nsAString &aStorageName);
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   bool mIsWatchingFile;
   bool mAllowedToWatchFile;
 
   nsresult Notify(const char* aReason, class DeviceStorageFile* aFile);
 
   friend class WatchFileEvent;
   friend class DeviceStorageRequest;
 
+  class VolumeNameCache : public mozilla::RefCounted<VolumeNameCache>
+  {
+  public:
+    nsTArray<nsString>  mVolumeNames;
+  };
+  static mozilla::StaticRefPtr<VolumeNameCache> sVolumeNameCache;
+
 #ifdef MOZ_WIDGET_GONK
-  void DispatchMountChangeEvent(nsAString& aType);
+  void DispatchMountChangeEvent(nsAString& aVolumeName,
+                                nsAString& aVolumeStatus);
 #endif
 
   // nsIDOMDeviceStorage.type
   enum {
       DEVICE_STORAGE_TYPE_DEFAULT = 0,
       DEVICE_STORAGE_TYPE_SHARED,
       DEVICE_STORAGE_TYPE_EXTERNAL
   };
--- a/dom/devicestorage/DeviceStorageRequestChild.cpp
+++ b/dom/devicestorage/DeviceStorageRequestChild.cpp
@@ -46,17 +46,19 @@ DeviceStorageRequestChild::Recv__delete_
     {
       ErrorResponse r = aValue;
       mRequest->FireError(r.error());
       break;
     }
 
     case DeviceStorageResponseValue::TSuccessResponse:
     {
-      JS::Value result = StringToJsval(mRequest->GetOwner(), mFile->mPath);
+      nsString compositePath;
+      mFile->GetCompositePath(compositePath);
+      JS::Value result = StringToJsval(mRequest->GetOwner(), compositePath);
       mRequest->FireSuccess(result);
       break;
     }
 
     case DeviceStorageResponseValue::TBlobResponse:
     {
       BlobResponse r = aValue;
       BlobChild* actor = static_cast<BlobChild*>(r.blobChild());
@@ -96,17 +98,18 @@ DeviceStorageRequestChild::Recv__delete_
     case DeviceStorageResponseValue::TEnumerationResponse:
     {
       EnumerationResponse r = aValue;
       nsDOMDeviceStorageCursor* cursor = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
 
       uint32_t count = r.paths().Length();
       for (uint32_t i = 0; i < count; i++) {
         nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(r.type(),
-                                                                r.relpath(),
+                                                                r.paths()[i].storageName(),
+                                                                r.rootdir(),
                                                                 r.paths()[i].name());
         cursor->mFiles.AppendElement(dsf);
       }
 
       nsCOMPtr<ContinueCursorEvent> event = new ContinueCursorEvent(cursor);
       event->Continue();
       break;
     }
--- a/dom/devicestorage/DeviceStorageRequestParent.cpp
+++ b/dom/devicestorage/DeviceStorageRequestParent.cpp
@@ -32,17 +32,18 @@ DeviceStorageRequestParent::DeviceStorag
 void
 DeviceStorageRequestParent::Dispatch()
 {
   switch (mParams.type()) {
     case DeviceStorageParams::TDeviceStorageAddParams:
     {
       DeviceStorageAddParams p = mParams;
 
-      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(p.type(), p.relpath());
+      nsRefPtr<DeviceStorageFile> dsf =
+        new DeviceStorageFile(p.type(), p.storageName(), p.relpath());
 
       BlobParent* bp = static_cast<BlobParent*>(p.blobParent());
       nsCOMPtr<nsIDOMBlob> blob = bp->GetBlob();
 
       nsCOMPtr<nsIInputStream> stream;
       blob->GetInternalStream(getter_AddRefs(stream));
 
       nsRefPtr<CancelableRunnable> r = new WriteFileEvent(this, dsf, stream);
@@ -51,79 +52,85 @@ DeviceStorageRequestParent::Dispatch()
       NS_ASSERTION(target, "Must have stream transport service");
       target->Dispatch(r, NS_DISPATCH_NORMAL);
       break;
     }
 
     case DeviceStorageParams::TDeviceStorageGetParams:
     {
       DeviceStorageGetParams p = mParams;
-      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(p.type(), p.rootDir(), p.relpath());
+      nsRefPtr<DeviceStorageFile> dsf =
+        new DeviceStorageFile(p.type(), p.storageName(), p.rootDir(), p.relpath());
       nsRefPtr<CancelableRunnable> r = new ReadFileEvent(this, dsf);
 
       nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
       NS_ASSERTION(target, "Must have stream transport service");
       target->Dispatch(r, NS_DISPATCH_NORMAL);
       break;
     }
 
     case DeviceStorageParams::TDeviceStorageDeleteParams:
     {
       DeviceStorageDeleteParams p = mParams;
 
-      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(p.type(), p.relpath());
+      nsRefPtr<DeviceStorageFile> dsf =
+        new DeviceStorageFile(p.type(), p.storageName(), p.relpath());
       nsRefPtr<CancelableRunnable> r = new DeleteFileEvent(this, dsf);
 
       nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
       NS_ASSERTION(target, "Must have stream transport service");
       target->Dispatch(r, NS_DISPATCH_NORMAL);
       break;
     }
 
     case DeviceStorageParams::TDeviceStorageFreeSpaceParams:
     {
       DeviceStorageFreeSpaceParams p = mParams;
 
-      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(p.type(), p.relpath());
+      nsRefPtr<DeviceStorageFile> dsf =
+        new DeviceStorageFile(p.type(), p.storageName());
       nsRefPtr<FreeSpaceFileEvent> r = new FreeSpaceFileEvent(this, dsf);
 
       nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
       NS_ASSERTION(target, "Must have stream transport service");
       target->Dispatch(r, NS_DISPATCH_NORMAL);
       break;
     }
 
     case DeviceStorageParams::TDeviceStorageUsedSpaceParams:
     {
       DeviceStorageUsedSpaceCache* usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet();
       NS_ASSERTION(usedSpaceCache, "DeviceStorageUsedSpaceCache is null");
 
       DeviceStorageUsedSpaceParams p = mParams;
 
-      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(p.type(), p.relpath());
+      nsRefPtr<DeviceStorageFile> dsf =
+        new DeviceStorageFile(p.type(), p.storageName());
       nsRefPtr<UsedSpaceFileEvent> r = new UsedSpaceFileEvent(this, dsf);
 
       usedSpaceCache->Dispatch(r);
       break;
     }
 
     case DeviceStorageParams::TDeviceStorageAvailableParams:
     {
       DeviceStorageAvailableParams p = mParams;
 
-      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(p.type(), p.relpath());
+      nsRefPtr<DeviceStorageFile> dsf =
+        new DeviceStorageFile(p.type(), p.storageName());
       nsRefPtr<PostAvailableResultEvent> r = new PostAvailableResultEvent(this, dsf);
       NS_DispatchToMainThread(r);
       break;
     }
 
     case DeviceStorageParams::TDeviceStorageEnumerationParams:
     {
       DeviceStorageEnumerationParams p = mParams;
-      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(p.type(), p.relpath(), NS_LITERAL_STRING(""));
+      nsRefPtr<DeviceStorageFile> dsf
+        = new DeviceStorageFile(p.type(), p.storageName(), p.rootdir(), NS_LITERAL_STRING(""));
       nsRefPtr<CancelableRunnable> r = new EnumerateFileEvent(this, dsf, p.since());
 
       nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
       NS_ASSERTION(target, "Must have stream transport service");
       target->Dispatch(r, NS_DISPATCH_NORMAL);
       break;
     }
     default:
@@ -346,17 +353,19 @@ DeviceStorageRequestParent::PostBlobSucc
 
 nsresult
 DeviceStorageRequestParent::PostBlobSuccessEvent::CancelableRun() {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   nsString mime;
   CopyASCIItoUTF16(mMimeType, mime);
 
-  nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(mFile->mPath, mime, mLength, mFile->mFile, mLastModificationDate);
+  nsString  compositePath;
+  mFile->GetCompositePath(compositePath);
+  nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(compositePath, mime, mLength, mFile->mFile, mLastModificationDate);
 
   ContentParent* cp = static_cast<ContentParent*>(mParent->Manager());
   BlobParent* actor = cp->GetOrCreateActorForBlob(blob);
   if (!actor) {
     ErrorResponse response(NS_LITERAL_STRING(POST_ERROR_EVENT_UNKNOWN));
     unused << mParent->Send__delete__(mParent, response);
     return NS_OK;
   }
@@ -482,23 +491,22 @@ DeviceStorageRequestParent::FreeSpaceFil
 {
 }
 
 nsresult
 DeviceStorageRequestParent::FreeSpaceFileEvent::CancelableRun()
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
-  nsCOMPtr<nsIRunnable> r;
   int64_t freeSpace = 0;
-  nsresult rv = mFile->mFile->GetDiskSpaceAvailable(&freeSpace);
-  if (NS_FAILED(rv)) {
-    freeSpace = 0;
+  if (mFile) {
+    mFile->GetDiskFreeSpace(&freeSpace);
   }
 
+  nsCOMPtr<nsIRunnable> r;
   r = new PostFreeSpaceResultEvent(mParent, static_cast<uint64_t>(freeSpace));
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 DeviceStorageRequestParent::UsedSpaceFileEvent::UsedSpaceFileEvent(DeviceStorageRequestParent* aParent,
                                                                    DeviceStorageFile* aFile)
   : CancelableRunnable(aParent)
@@ -510,50 +518,30 @@ DeviceStorageRequestParent::UsedSpaceFil
 {
 }
 
 nsresult
 DeviceStorageRequestParent::UsedSpaceFileEvent::CancelableRun()
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
-  DeviceStorageUsedSpaceCache* usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet();
-  NS_ASSERTION(usedSpaceCache, "DeviceStorageUsedSpaceCache is null");
-
+  uint64_t picturesUsage = 0, videosUsage = 0, musicUsage = 0, totalUsage = 0;
+  mFile->AccumDiskUsage(&picturesUsage, &videosUsage,
+                        &musicUsage, &totalUsage);
   nsCOMPtr<nsIRunnable> r;
-
-  uint64_t usedSize;
-  nsresult rv = usedSpaceCache->GetUsedSizeForType(mFile->mStorageType, &usedSize);
-  if (NS_SUCCEEDED(rv)) {
-    r = new PostUsedSpaceResultEvent(mParent, mFile->mStorageType, usedSize);
-    NS_DispatchToMainThread(r);
-    return NS_OK;
+  if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
+    r = new PostUsedSpaceResultEvent(mParent, mFile->mStorageType, picturesUsage);
   }
-
-  uint64_t picturesUsage = 0, videosUsage = 0, musicUsage = 0, totalUsage = 0;
-  DeviceStorageFile::DirectoryDiskUsage(mFile->mFile, &picturesUsage,
-                                        &videosUsage, &musicUsage,
-                                        &totalUsage);
-  if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_APPS)) {
-    // Don't cache this since it's for a different volume from the media.
-    r = new PostUsedSpaceResultEvent(mParent, mFile->mStorageType, totalUsage);
+  else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
+    r = new PostUsedSpaceResultEvent(mParent, mFile->mStorageType, videosUsage);
+  }
+  else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
+    r = new PostUsedSpaceResultEvent(mParent, mFile->mStorageType, musicUsage);
   } else {
-    usedSpaceCache->SetUsedSizes(picturesUsage, videosUsage, musicUsage, totalUsage);
-
-    if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
-      r = new PostUsedSpaceResultEvent(mParent, mFile->mStorageType, picturesUsage);
-    }
-    else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
-      r = new PostUsedSpaceResultEvent(mParent, mFile->mStorageType, videosUsage);
-    }
-    else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
-      r = new PostUsedSpaceResultEvent(mParent, mFile->mStorageType, musicUsage);
-    } else {
-      r = new PostUsedSpaceResultEvent(mParent, mFile->mStorageType, totalUsage);
-    }
+    r = new PostUsedSpaceResultEvent(mParent, mFile->mStorageType, totalUsage);
   }
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 DeviceStorageRequestParent::ReadFileEvent::ReadFileEvent(DeviceStorageRequestParent* aParent,
                                                          DeviceStorageFile* aFile)
   : CancelableRunnable(aParent)
@@ -622,32 +610,34 @@ DeviceStorageRequestParent::EnumerateFil
 }
 
 nsresult
 DeviceStorageRequestParent::EnumerateFileEvent::CancelableRun()
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   nsCOMPtr<nsIRunnable> r;
-  bool check = false;
-  mFile->mFile->Exists(&check);
-  if (!check) {
-    r = new PostErrorEvent(mParent, POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
-    NS_DispatchToMainThread(r);
-    return NS_OK;
+  if (mFile->mFile) {
+    bool check = false;
+    mFile->mFile->Exists(&check);
+    if (!check) {
+      r = new PostErrorEvent(mParent, POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
+      NS_DispatchToMainThread(r);
+      return NS_OK;
+    }
   }
 
   nsTArray<nsRefPtr<DeviceStorageFile> > files;
   mFile->CollectFiles(files, mSince);
 
   InfallibleTArray<DeviceStorageFileValue> values;
 
   uint32_t count = files.Length();
   for (uint32_t i = 0; i < count; i++) {
-    DeviceStorageFileValue dsvf(files[i]->mPath);
+    DeviceStorageFileValue dsvf(files[i]->mStorageName, files[i]->mPath);
     values.AppendElement(dsvf);
   }
 
   r = new PostEnumerationSuccessEvent(mParent, mFile->mStorageType, mFile->mRootDir, values);
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
@@ -684,28 +674,20 @@ DeviceStorageRequestParent::PostAvailabl
 {
 }
 
 nsresult
 DeviceStorageRequestParent::PostAvailableResultEvent::CancelableRun()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  nsString state;
-  state.Assign(NS_LITERAL_STRING("available"));
-#ifdef MOZ_WIDGET_GONK
-  nsString path;
-  nsresult rv = mFile->mFile->GetPath(path);
-  if (NS_SUCCEEDED(rv)) {
-    rv = GetSDCardStatus(path, state);
+  nsString state = NS_LITERAL_STRING("unavailable");
+  if (mFile) {
+    mFile->GetStatus(state);
   }
-  if (NS_FAILED(rv)) {
-    state.Assign(NS_LITERAL_STRING("unavailable"));
-  }
-#endif
 
   AvailableStorageResponse response(state);
   unused << mParent->Send__delete__(mParent, response);
   return NS_OK;
 }
 
 } // namespace devicestorage
 } // namespace dom
--- a/dom/devicestorage/PDeviceStorageRequest.ipdl
+++ b/dom/devicestorage/PDeviceStorageRequest.ipdl
@@ -22,23 +22,24 @@ struct SuccessResponse
 
 struct BlobResponse
 {
   PBlob blob;
 };
 
 struct DeviceStorageFileValue
 {
+  nsString storageName;
   nsString name;
 };
 
 struct EnumerationResponse
 {
   nsString type;
-  nsString relpath;
+  nsString rootdir;
   DeviceStorageFileValue[] paths;
 };
 
 struct FreeSpaceStorageResponse
 {
   uint64_t freeBytes;
 };
 
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -62,17 +62,16 @@ using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::devicestorage;
 
 #include "nsDirectoryServiceDefs.h"
 
 StaticAutoPtr<DeviceStorageUsedSpaceCache> DeviceStorageUsedSpaceCache::sDeviceStorageUsedSpaceCache;
 
 DeviceStorageUsedSpaceCache::DeviceStorageUsedSpaceCache()
-  : mDirty(true)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
                                  NS_LITERAL_CSTRING("DeviceStorageUsedSpaceCache I/O"));
 
 }
 
@@ -89,84 +88,110 @@ DeviceStorageUsedSpaceCache::CreateOrGet
 
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   sDeviceStorageUsedSpaceCache = new DeviceStorageUsedSpaceCache();
   ClearOnShutdown(&sDeviceStorageUsedSpaceCache);
   return sDeviceStorageUsedSpaceCache;
 }
 
-nsresult
-DeviceStorageUsedSpaceCache::GetPicturesUsedSize(uint64_t* usedSize) {
-  if (mDirty == true) {
-    return NS_ERROR_NOT_AVAILABLE;
+TemporaryRef<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)) {
+      return cacheEntry;
+    }
   }
-  *usedSize = mPicturesUsedSize;
-  return NS_OK;
-}
-
-nsresult
-DeviceStorageUsedSpaceCache::GetMusicUsedSize(uint64_t* usedSize) {
-  if (mDirty == true) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-  *usedSize = mMusicUsedSize;
-  return NS_OK;
+  return nullptr;
 }
 
 nsresult
-DeviceStorageUsedSpaceCache::GetVideosUsedSize(uint64_t* usedSize) {
-  if (mDirty == true) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-  *usedSize = mVideosUsedSize;
-  return NS_OK;
-}
-
-nsresult
-DeviceStorageUsedSpaceCache::GetTotalUsedSize(uint64_t* usedSize) {
-  if (mDirty == true) {
+DeviceStorageUsedSpaceCache::GetUsedSizeForType(const nsAString& aStorageType,
+                                                const nsAString& aStorageName,
+                                                uint64_t* usedSize)
+{
+  RefPtr<CacheEntry> cacheEntry = GetCacheEntry(aStorageName);
+  if (!cacheEntry || cacheEntry->mDirty) {
     return NS_ERROR_NOT_AVAILABLE;
   }
-  *usedSize = mTotalUsedSize;
-  return NS_OK;
-}
-
-nsresult
-DeviceStorageUsedSpaceCache::GetUsedSizeForType(const nsAString& aType,
-                                                uint64_t* usedSize) {
-  if (aType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
-    return GetPicturesUsedSize(usedSize);
+
+  printf_stderr("Getting used size for %s from cache\n",
+                NS_LossyConvertUTF16toASCII(aStorageName).get());
+
+  if (aStorageType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
+    *usedSize = cacheEntry->mPicturesUsedSize;
+    return NS_OK;
   }
 
-  if (aType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
-    return GetVideosUsedSize(usedSize);
+  if (aStorageType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
+    *usedSize = cacheEntry->mVideosUsedSize;
+    return NS_OK;
   }
 
-  if (aType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
-    return GetMusicUsedSize(usedSize);
+  if (aStorageType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
+    *usedSize = cacheEntry->mMusicUsedSize;
+    return NS_OK;
   }
 
-  if (aType.EqualsLiteral(DEVICESTORAGE_SDCARD)) {
-    return GetTotalUsedSize(usedSize);
+  if (aStorageType.EqualsLiteral(DEVICESTORAGE_SDCARD)) {
+    *usedSize = cacheEntry->mTotalUsedSize;
+    return NS_OK;
   }
 
   return NS_ERROR_FAILURE;
 }
 
+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;
+  }
+
+  printf_stderr("Getting used size for %s from cache\n",
+                NS_LossyConvertUTF16toASCII(aStorageName).get());
+
+  *aPicturesSoFar += cacheEntry->mPicturesUsedSize;
+  *aVideosSoFar += cacheEntry->mVideosUsedSize;
+  *aMusicSoFar += cacheEntry->mMusicUsedSize;
+  *aTotalSoFar += cacheEntry->mTotalUsedSize;
+
+  return NS_OK;
+}
+
 void
-DeviceStorageUsedSpaceCache::SetUsedSizes(uint64_t aPictureSize,
+DeviceStorageUsedSpaceCache::SetUsedSizes(const nsAString& aStorageName,
+                                          uint64_t aPictureSize,
                                           uint64_t aVideosSize,
                                           uint64_t aMusicSize,
-                                          uint64_t aTotalUsedSize) {
-    mPicturesUsedSize = aPictureSize;
-    mVideosUsedSize = aVideosSize;
-    mMusicUsedSize = aMusicSize;
-    mTotalUsedSize = aTotalUsedSize;
-    mDirty = false;
+                                          uint64_t aTotalUsedSize)
+{
+  RefPtr<CacheEntry> cacheEntry = GetCacheEntry(aStorageName);
+  if (!cacheEntry) {
+    cacheEntry = new CacheEntry;
+    cacheEntry->mStorageName = aStorageName;
+    mCacheEntries.AppendElement(cacheEntry);
+  }
+
+  printf_stderr("Setting cache of used sizes for %s\n",
+                NS_LossyConvertUTF16toASCII(aStorageName).get());
+
+  cacheEntry->mPicturesUsedSize = aPictureSize;
+  cacheEntry->mVideosUsedSize = aVideosSize;
+  cacheEntry->mMusicUsedSize = aMusicSize;
+  cacheEntry->mTotalUsedSize = aTotalUsedSize;
+  cacheEntry->mDirty = false;
 }
 
 class GlobalDirs : public RefCounted<GlobalDirs>
 {
 public:
 #if !defined(MOZ_WIDGET_GONK)
   nsCOMPtr<nsIFile> pictures;
   nsCOMPtr<nsIFile> videos;
@@ -295,29 +320,36 @@ DeviceStorageTypeChecker::Check(const ns
   return false;
 }
 
 void
 DeviceStorageTypeChecker::GetTypeFromFile(nsIFile* aFile, nsAString& aType)
 {
   NS_ASSERTION(aFile, "Calling Check without a file");
 
-  aType.AssignLiteral("Unknown");
-
   nsString path;
   aFile->GetPath(path);
 
-  int32_t dotIdx = path.RFindChar(PRUnichar('.'));
+  GetTypeFromFileName(path, aType);
+}
+
+void
+DeviceStorageTypeChecker::GetTypeFromFileName(const nsAString& aFileName, nsAString& aType)
+{
+  aType.AssignLiteral("Unknown");
+
+  nsString fileName(aFileName);
+  int32_t dotIdx = fileName.RFindChar(PRUnichar('.'));
   if (dotIdx == kNotFound) {
     return;
   }
 
   nsAutoString extensionMatch;
   extensionMatch.AssignLiteral("*");
-  extensionMatch.Append(Substring(path, dotIdx));
+  extensionMatch.Append(Substring(aFileName, dotIdx));
   extensionMatch.AppendLiteral(";");
 
   if (CaseInsensitiveFindInReadable(extensionMatch, mPicturesExtensions)) {
     aType.AssignLiteral(DEVICESTORAGE_PICTURES);
   }
   else if (CaseInsensitiveFindInReadable(extensionMatch, mVideosExtensions)) {
     aType.AssignLiteral(DEVICESTORAGE_VIDEOS);
   }
@@ -362,24 +394,29 @@ DeviceStorageTypeChecker::GetAccessForRe
       aAccessResult.AssignLiteral("create");
       break;
     default:
       aAccessResult.AssignLiteral("undefined");
   }
   return NS_OK;
 }
 
+//static
 bool
 DeviceStorageTypeChecker::IsVolumeBased(const nsAString& aType)
 {
+#ifdef MOZ_WIDGET_GONK
   // The apps 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 !aType.EqualsLiteral(DEVICESTORAGE_APPS);
+#else
+  return false;
+#endif
 }
 
 NS_IMPL_ISUPPORTS1(FileUpdateDispatcher, nsIObserver)
 
 mozilla::StaticRefPtr<FileUpdateDispatcher> FileUpdateDispatcher::sSingleton;
 
 FileUpdateDispatcher*
 FileUpdateDispatcher::GetSingleton()
@@ -404,16 +441,17 @@ FileUpdateDispatcher::Observe(nsISupport
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
 
     DeviceStorageFile* file = static_cast<DeviceStorageFile*>(aSubject);
     if (!file || !file->mFile) {
       NS_WARNING("Device storage file looks invalid!");
       return NS_OK;
     }
     ContentChild::GetSingleton()->SendFilePathUpdateNotify(file->mStorageType,
+                                                           file->mStorageName,
                                                            file->mPath,
                                                            NS_ConvertUTF16toUTF8(aData));
   } else {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->NotifyObservers(aSubject, "file-watcher-update", aData);
   }
   return NS_OK;
 }
@@ -435,65 +473,95 @@ public:
     nsString data;
     CopyASCIItoUTF16(mType, data);
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
 
     obs->NotifyObservers(mFile, "file-watcher-notify", data.get());
 
     DeviceStorageUsedSpaceCache* usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet();
     NS_ASSERTION(usedSpaceCache, "DeviceStorageUsedSpaceCache is null");
-    usedSpaceCache->Invalidate();
+    usedSpaceCache->Invalidate(mFile->mStorageName);
     return NS_OK;
   }
 
 private:
   nsRefPtr<DeviceStorageFile> mFile;
   nsCString mType;
 };
 
 DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType,
+                                     const nsAString& aStorageName,
                                      const nsAString& aRootDir,
                                      const nsAString& aPath)
-  : mPath(aPath)
-  , mStorageType(aStorageType)
+  : mStorageType(aStorageType)
+  , mStorageName(aStorageName)
   , mRootDir(aRootDir)
+  , mPath(aPath)
   , mEditable(false)
 {
-  Init(aStorageType);
+  Init();
   AppendRelativePath(mRootDir);
   if (!mPath.EqualsLiteral("")) {
     AppendRelativePath(mPath);
   }
   NormalizeFilePath();
 }
 
 DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType,
+                                     const nsAString& aStorageName,
                                      const nsAString& aPath)
-  : mPath(aPath)
-  , mStorageType(aStorageType)
+  : mStorageType(aStorageType)
+  , mStorageName(aStorageName)
+  , mPath(aPath)
   , mEditable(false)
 {
-  Init(aStorageType);
+  Init();
   AppendRelativePath(aPath);
   NormalizeFilePath();
 }
 
-DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType)
+DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType,
+                                     const nsAString& aStorageName)
   : mStorageType(aStorageType)
+  , mStorageName(aStorageName)
   , mEditable(false)
 {
-  Init(aStorageType);
+  Init();
 }
 
 void
-DeviceStorageFile::Init(const nsAString& aStorageType)
+DeviceStorageFile::Dump(const char* label)
 {
-  // The hard-coded sdcard below will change as part of bug 858416
-  DeviceStorageFile::GetRootDirectoryForType(aStorageType,
-                                             NS_LITERAL_STRING("sdcard"),
+  nsString path;
+  if (mFile) {
+    mFile->GetPath(path);
+  } else {
+    path = NS_LITERAL_STRING("(null)");
+  }
+  const char* ptStr;
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    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();
   NS_ASSERTION(typeChecker, "DeviceStorageTypeChecker is null");
 }
 
 static void
 InitDirs()
@@ -542,95 +610,163 @@ InitDirs()
     if (sDirs->temp) {
       sDirs->temp->AppendRelativeNativePath(NS_LITERAL_CSTRING("device-storage-testing"));
       sDirs->temp->Create(nsIFile::DIRECTORY_TYPE, 0777);
       sDirs->temp->Normalize();
     }
   }
 }
 
+bool DeviceStorageFile::IsComposite()
+{
+  return DeviceStorageTypeChecker::IsVolumeBased(mStorageType) && 
+    mStorageName.EqualsLiteral("");
+}
+
 void
-DeviceStorageFile::GetRootDirectoryForType(const nsAString &aType, const
-                                           nsAString &aVolName,
+DeviceStorageFile::GetCompositePath(nsAString &aCompositePath)
+{
+  aCompositePath.AssignLiteral("");
+  if (!mStorageName.EqualsLiteral("")) {
+    aCompositePath.AppendLiteral("/");
+    aCompositePath.Append(mStorageName);
+    aCompositePath.AppendLiteral("/");
+  }
+  if (!mRootDir.EqualsLiteral("")) {
+    aCompositePath.Append(mRootDir);
+    aCompositePath.AppendLiteral("/");
+  }
+  aCompositePath.Append(mPath);
+}
+
+
+void
+DeviceStorageFile::GetRootDirectoryForType(const nsAString& aStorageType,
+                                           const nsAString& aStorageName,
                                            nsIFile** aFile)
 {
   nsCOMPtr<nsIFile> f;
+  *aFile = nullptr;
 
   InitDirs();
 
 #ifdef MOZ_WIDGET_GONK
-  nsString volMountPoint(NS_LITERAL_STRING("/sdcard"));
-  if (!aVolName.EqualsLiteral("")) {
+  nsString volMountPoint;
+  if (DeviceStorageTypeChecker::IsVolumeBased(aStorageType)) {
+    if (aStorageName.EqualsLiteral("")) {
+      // This DeviceStorageFile is for a composite device. Since the composite
+      // device doesn't have a root, we just allow mFile to be null. These
+      // should get resolved to real device objects later on.
+      return;
+    }
+
     nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
-    if (vs) {
-      nsresult rv;
-      nsCOMPtr<nsIVolume> vol;
-      rv = vs->GetVolumeByName(aVolName, getter_AddRefs(vol));
-      if (NS_SUCCEEDED(rv)) {
-        vol->GetMountPoint(volMountPoint);
-      }
-    }
+    NS_ENSURE_TRUE_VOID(vs);
+    nsresult rv;
+    nsCOMPtr<nsIVolume> vol;
+    rv = vs->GetVolumeByName(aStorageName, getter_AddRefs(vol));
+    NS_ENSURE_SUCCESS_VOID(rv);
+    vol->GetMountPoint(volMountPoint);
   }
 #endif
 
   // Picture directory
-  if (aType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
+  if (aStorageType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
 #ifdef MOZ_WIDGET_GONK
     NS_NewLocalFile(volMountPoint, false, getter_AddRefs(f));
 #else
     f = sDirs->pictures;
 #endif
   }
 
   // Video directory
-  else if (aType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
+  else if (aStorageType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
 #ifdef MOZ_WIDGET_GONK
     NS_NewLocalFile(volMountPoint, false, getter_AddRefs(f));
 #else
     f = sDirs->videos;
 #endif
   }
 
   // Music directory
-  else if (aType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
+  else if (aStorageType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
 #ifdef MOZ_WIDGET_GONK
     NS_NewLocalFile(volMountPoint, false, getter_AddRefs(f));
 #else
     f = sDirs->music;
 #endif
   }
 
   // Apps directory
-  else if (aType.EqualsLiteral(DEVICESTORAGE_APPS)) {
+  else if (aStorageType.EqualsLiteral(DEVICESTORAGE_APPS)) {
 #ifdef MOZ_WIDGET_GONK
     NS_NewLocalFile(NS_LITERAL_STRING("/data"), false, getter_AddRefs(f));
 #else
     f = sDirs->apps;
 #endif
   }
 
    // default SDCard
-   else if (aType.EqualsLiteral(DEVICESTORAGE_SDCARD)) {
+   else if (aStorageType.EqualsLiteral(DEVICESTORAGE_SDCARD)) {
 #ifdef MOZ_WIDGET_GONK
      NS_NewLocalFile(volMountPoint, false, getter_AddRefs(f));
 #else
      f = sDirs->sdcard;
 #endif
   }
 
   // in testing, we default all device storage types to a temp directory
   if (f && mozilla::Preferences::GetBool("device.storage.testing", false)) {
     f = sDirs->temp;
   }
 
   if (f) {
     f->Clone(aFile);
+  }
+}
+
+//static
+already_AddRefed<DeviceStorageFile>
+DeviceStorageFile::CreateUnique(nsAString& aFileName,
+                                uint32_t aFileType,
+                                uint32_t aFileAttributes)
+{
+  DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet();
+  NS_ASSERTION(typeChecker, "DeviceStorageTypeChecker is null");
+
+  nsString storageType;
+  typeChecker->GetTypeFromFileName(aFileName, storageType);
+
+  nsString storageName;
+  nsString storagePath;
+  if (!nsDOMDeviceStorage::ParseCompositePath(aFileName, storageName, storagePath)) {
+    return nullptr;
+  }
+  nsRefPtr<DeviceStorageFile> dsf =
+    new DeviceStorageFile(storageType, storageName, storagePath);
+  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.
+  
+  int32_t lastSlashIndex = dsf->mPath.RFindChar('/');
+  if (lastSlashIndex == kNotFound) {
+    dsf->mPath.AssignLiteral("");
   } else {
-    *aFile = nullptr;
+    dsf->mPath = Substring(dsf->mPath, 0, lastSlashIndex);
   }
+  nsString leafName;
+  dsf->mFile->GetLeafName(leafName);
+  dsf->AppendRelativePath(leafName);
+
+  return dsf.forget();
 }
 
 void
 DeviceStorageFile::SetPath(const nsAString& aPath) {
   mPath.Assign(aPath);
   NormalizeFilePath();
 }
 
@@ -686,16 +822,19 @@ DeviceStorageFile::NormalizeFilePath() {
     if (PRUnichar('\\') == *cur)
       *cur = PRUnichar('/');
   }
 #endif
 }
 
 void
 DeviceStorageFile::AppendRelativePath(const nsAString& aPath) {
+  if (!mFile) {
+    return;
+  }
 #if defined(XP_WIN)
   // replace forward slashes with backslashes,
   // since nsLocalFileWin chokes on them
   nsString temp;
   temp.Assign(aPath);
 
   PRUnichar* cur = temp.BeginWriting();
   PRUnichar* end = temp.EndWriting();
@@ -708,17 +847,17 @@ DeviceStorageFile::AppendRelativePath(co
 #else
   mFile->AppendRelativePath(aPath);
 #endif
 }
 
 nsresult
 DeviceStorageFile::Write(nsIInputStream* aInputStream)
 {
-  if (!aInputStream) {
+  if (!aInputStream || !mFile) {
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv = mFile->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
@@ -759,17 +898,21 @@ DeviceStorageFile::Write(nsIInputStream*
   outputStream->Close();
   if (NS_FAILED(rv)) {
     return rv;
   }
   return NS_OK;
 }
 
 nsresult
-DeviceStorageFile::Write(InfallibleTArray<uint8_t>& aBits) {
+DeviceStorageFile::Write(InfallibleTArray<uint8_t>& aBits)
+{
+  if (!mFile) {
+    return NS_ERROR_FAILURE;
+  }
 
   nsresult rv = mFile->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   nsCOMPtr<IOEventComplete> iocomplete = new IOEventComplete(this, "created");
   NS_DispatchToMainThread(iocomplete);
@@ -794,16 +937,20 @@ DeviceStorageFile::Write(InfallibleTArra
   return NS_OK;
 }
 
 nsresult
 DeviceStorageFile::Remove()
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
+  if (!mFile) {
+    return NS_ERROR_FAILURE;
+  }
+
   bool check;
   nsresult rv = mFile->Exists(&check);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   if (!check) {
     return NS_OK;
@@ -818,29 +965,43 @@ DeviceStorageFile::Remove()
   NS_DispatchToMainThread(iocomplete);
   return NS_OK;
 }
 
 void
 DeviceStorageFile::CollectFiles(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles,
                                 PRTime aSince)
 {
-  nsString rootPath;
-  nsresult rv = mFile->GetPath(rootPath);
-  if (NS_FAILED(rv)) {
+  nsString fullRootPath;
+
+  if (IsComposite()) {
+    nsDOMDeviceStorage::VolumeNameArray volNames;
+    nsDOMDeviceStorage::GetOrderedVolumeNames(volNames);
+    nsDOMDeviceStorage::VolumeNameArray::size_type numVolumes = volNames.Length();
+    nsDOMDeviceStorage::VolumeNameArray::index_type i;
+    for (i = 0; i < numVolumes; i++) {
+      DeviceStorageFile dsf(mStorageType, volNames[i], mRootDir, NS_LITERAL_STRING(""));
+      dsf.mFile->GetPath(fullRootPath);
+      dsf.collectFilesInternal(aFiles, aSince, fullRootPath);
+    }
     return;
   }
-  return collectFilesInternal(aFiles, aSince, rootPath);
+  mFile->GetPath(fullRootPath);
+  collectFilesInternal(aFiles, aSince, fullRootPath);
 }
 
 void
 DeviceStorageFile::collectFilesInternal(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles,
                                         PRTime aSince,
                                         nsAString& aRootPath)
 {
+  if (!mFile || !IsAvailable()) {
+    return;
+  }
+
   nsCOMPtr<nsISimpleEnumerator> e;
   mFile->GetDirectoryEntries(getter_AddRefs(e));
 
   if (!e) {
     return;
   }
 
   nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
@@ -871,51 +1032,108 @@ DeviceStorageFile::collectFilesInternal(
       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, mRootDir, newPath);
+      DeviceStorageFile dsf(mStorageType, mStorageName, mRootDir, newPath);
       dsf.collectFilesInternal(aFiles, aSince, aRootPath);
     } else if (isFile) {
-      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, mRootDir, newPath);
+      nsRefPtr<DeviceStorageFile> dsf =
+        new DeviceStorageFile(mStorageType, mStorageName, mRootDir, newPath);
       aFiles.AppendElement(dsf);
     }
   }
 }
 
 void
-DeviceStorageFile::DirectoryDiskUsage(nsIFile* aFile,
-                                      uint64_t* aPicturesSoFar,
-                                      uint64_t* aVideosSoFar,
-                                      uint64_t* aMusicSoFar,
-                                      uint64_t* aTotalSoFar) {
+DeviceStorageFile::AccumDiskUsage(uint64_t* aPicturesSoFar,
+                                  uint64_t* aVideosSoFar,
+                                  uint64_t* aMusicSoFar,
+                                  uint64_t* aTotalSoFar)
+{
+  if (IsComposite()) {
+    nsDOMDeviceStorage::VolumeNameArray volNames;
+    nsDOMDeviceStorage::GetOrderedVolumeNames(volNames);
+    nsDOMDeviceStorage::VolumeNameArray::size_type numVolumes = volNames.Length();
+    nsDOMDeviceStorage::VolumeNameArray::index_type i;
+    for (i = 0; i < numVolumes; i++) {
+      DeviceStorageFile dsf(mStorageType, volNames[i]);
+      dsf.AccumDiskUsage(aPicturesSoFar, aVideosSoFar,
+                         aMusicSoFar, aTotalSoFar);
+    }
+    return;
+  }
+printf_stderr("AccumDiskUsage '%s'\n",
+              NS_LossyConvertUTF16toASCII(mStorageName).get());
+
+  if (!IsAvailable()) {
+printf_stderr("AccumDiskUsage Not Available '%s'\n",
+              NS_LossyConvertUTF16toASCII(mStorageName).get());
+    return;
+  }
+
+  uint64_t pictureUsage = 0, videoUsage = 0, musicUsage = 0, totalUsage = 0;
+
+  if (DeviceStorageTypeChecker::IsVolumeBased(mStorageType)) {
+    DeviceStorageUsedSpaceCache* usedSpaceCache =
+      DeviceStorageUsedSpaceCache::CreateOrGet();
+    NS_ASSERTION(usedSpaceCache, "DeviceStorageUsedSpaceCache is null");
+    nsresult rv = usedSpaceCache->AccumUsedSizes(mStorageName, 
+                                                 aPicturesSoFar, aVideosSoFar,
+                                                 aMusicSoFar, aTotalSoFar);
+    if (NS_SUCCEEDED(rv)) {
+printf_stderr("Accumulated from cache\n");
+      return;
+    }
+printf_stderr("Not cached 1 - calling AccumDirectoryUsage\n");
+    AccumDirectoryUsage(mFile, &pictureUsage, &videoUsage,
+                        &musicUsage, &totalUsage);
+    usedSpaceCache->SetUsedSizes(mStorageName, pictureUsage, videoUsage,
+                                 musicUsage, totalUsage);
+  } else {
+printf_stderr("Not cached 2 - calling AccumDirectoryUsage\n");
+    AccumDirectoryUsage(mFile, &pictureUsage, &videoUsage,
+                        &musicUsage, &totalUsage);
+  }
+printf_stderr("p=%llu v=%llu m=%llu t=%llu\n", 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) {
+printf_stderr("AccumDirectoryUsage aFile == null\n");
     return;
   }
 
   nsresult rv;
   nsCOMPtr<nsISimpleEnumerator> e;
   rv = aFile->GetDirectoryEntries(getter_AddRefs(e));
 
   if (NS_FAILED(rv) || !e) {
+printf_stderr("AccumDirectoryUsage failed to get directory entries\n");
     return;
   }
 
   nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
   NS_ASSERTION(files, "GetDirectoryEntries must return a nsIDirectoryEnumerator");
 
-  DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet();
-  if (!typeChecker) {
-    return;
-  }
-
   nsCOMPtr<nsIFile> f;
   while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(f))) && f) {
     bool isDir;
     rv = f->IsDirectory(&isDir);
     if (NS_FAILED(rv)) {
       continue;
     }
 
@@ -930,17 +1148,17 @@ DeviceStorageFile::DirectoryDiskUsage(ns
     if (NS_FAILED(rv)) {
       continue;
     }
 
     if (isLink) {
       // for now, lets just totally ignore symlinks.
       NS_WARNING("DirectoryDiskUsage ignores symlinks");
     } else if (isDir) {
-      DirectoryDiskUsage(f, aPicturesSoFar, aVideosSoFar, aMusicSoFar, aTotalSoFar);
+      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();
@@ -957,95 +1175,155 @@ DeviceStorageFile::DirectoryDiskUsage(ns
       else if (type.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
         *aMusicSoFar += size;
       }
       *aTotalSoFar += size;
     }
   }
 }
 
+void
+DeviceStorageFile::GetDiskFreeSpace(int64_t* aSoFar)
+{
+  DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet();
+  if (!typeChecker) {
+    return;
+  }
+  if (IsComposite()) {
+    nsDOMDeviceStorage::VolumeNameArray volNames;
+    nsDOMDeviceStorage::GetOrderedVolumeNames(volNames);
+    nsDOMDeviceStorage::VolumeNameArray::size_type numVolumes = volNames.Length();
+    nsDOMDeviceStorage::VolumeNameArray::index_type i;
+    for (i = 0; i < numVolumes; i++) {
+      DeviceStorageFile dsf(mStorageType, volNames[i]);
+      dsf.GetDiskFreeSpace(aSoFar);
+    }
+    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::GetStatus(nsAString& aStatus)
+{
+  DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet();
+  if (!typeChecker) {
+    return;
+  }
+  if (!typeChecker->IsVolumeBased(mStorageType)) {
+    aStatus.AssignLiteral("available");
+    return;
+  }
+
+  if (!mStorageName.EqualsLiteral("")) {
+    GetStatusInternal(mStorageName, aStatus);
+    return;
+  }
+
+  // We want a composite status.
+
+  aStatus.AssignLiteral("unavailable");
+
+  nsDOMDeviceStorage::VolumeNameArray volNames;
+  nsDOMDeviceStorage::GetOrderedVolumeNames(volNames);
+  nsDOMDeviceStorage::VolumeNameArray::size_type numVolumes = volNames.Length();
+  nsDOMDeviceStorage::VolumeNameArray::index_type i;
+  for (i = 0; i < numVolumes; i++) {
+    nsString  volStatus;
+    GetStatusInternal(volNames[i], volStatus);
+    if (volStatus.EqualsLiteral("available")) {
+      // We found an available volume. We can quit now, since the composite
+      // status is available if any are available
+      aStatus = volStatus;
+      return;
+    }
+    if (volStatus.EqualsLiteral("shared")) {
+      aStatus = volStatus;
+      // need to keep looking since we might find an available volume later.
+    }
+  }
+}
+
+void
+DeviceStorageFile::GetStatusInternal(nsAString& aStorageName, nsAString& aStatus)
+{
+  aStatus.AssignLiteral("unavailable");
+
+#ifdef MOZ_WIDGET_GONK
+  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
+  NS_ENSURE_TRUE_VOID(vs);
+
+  nsCOMPtr<nsIVolume> vol;
+  nsresult rv = vs->GetVolumeByName(aStorageName, getter_AddRefs(vol));
+  NS_ENSURE_SUCCESS_VOID(rv);
+  int32_t volState;
+  rv = vol->GetState(&volState);
+  NS_ENSURE_SUCCESS_VOID(rv);
+  if (volState == nsIVolume::STATE_MOUNTED) {
+    aStatus.AssignLiteral("available");
+  } else if (volState == nsIVolume::STATE_SHARED || volState == nsIVolume::STATE_SHAREDMNT) {
+    aStatus.AssignLiteral("shared");
+  }
+#endif
+}
+
 NS_IMPL_THREADSAFE_ISUPPORTS0(DeviceStorageFile)
 
 #ifdef MOZ_WIDGET_GONK
-nsresult
-GetSDCardStatus(nsAString& aPath, nsAString& aState) {
-  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
-  if (!vs) {
-    return NS_ERROR_FAILURE;
-  }
-  nsCOMPtr<nsIVolume> vol;
-  vs->GetVolumeByPath(aPath, getter_AddRefs(vol));
-  if (!vol) {
-    return NS_ERROR_FAILURE;
-  }
-
-  int32_t state;
-  nsresult rv = vol->GetState(&state);
-  if (NS_FAILED(rv)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (state == nsIVolume::STATE_MOUNTED) {
-    aState.AssignLiteral("available");
-  } else if (state == nsIVolume::STATE_SHARED || state == nsIVolume::STATE_SHAREDMNT) {
-    aState.AssignLiteral("shared");
-  } else {
-    aState.AssignLiteral("unavailable");
-  }
-  return NS_OK;
-}
-
 static void
 RegisterForSDCardChanges(nsIObserver* aObserver)
 {
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   obs->AddObserver(aObserver, NS_VOLUME_STATE_CHANGED, false);
 }
 
 static void
 UnregisterForSDCardChanges(nsIObserver* aObserver)
 {
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   obs->RemoveObserver(aObserver, NS_VOLUME_STATE_CHANGED);
 }
 #endif
 
 void
-nsDOMDeviceStorage::SetRootDirectoryForType(const nsAString& aType,
-                                            const nsAString& aVolName)
+nsDOMDeviceStorage::SetRootDirectoryForType(const nsAString& aStorageType,
+                                            const nsAString& aStorageName)
 {
   nsCOMPtr<nsIFile> f;
-  DeviceStorageFile::GetRootDirectoryForType(aType,
-                                             aVolName,
+  DeviceStorageFile::GetRootDirectoryForType(aStorageType,
+                                             aStorageName,
                                              getter_AddRefs(f));
 
-  mVolumeName = NS_LITERAL_STRING("");
 #ifdef MOZ_WIDGET_GONK
-  nsString volMountPoint(NS_LITERAL_STRING("/sdcard"));
-  if (!aVolName.EqualsLiteral("")) {
-    nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
-    if (vs) {
-      nsresult rv;
-      nsCOMPtr<nsIVolume> vol;
-      rv = vs->GetVolumeByName(aVolName, getter_AddRefs(vol));
-      if (NS_SUCCEEDED(rv)) {
-        vol->GetMountPoint(volMountPoint);
-        mVolumeName = aVolName;
-      }
-    }
-  }
-
   RegisterForSDCardChanges(this);
 #endif
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   obs->AddObserver(this, "file-watcher-update", false);
   obs->AddObserver(this, "disk-space-watcher", false);
   mRootDirectory = f;
-  mStorageType = aType;
+  mStorageType = aStorageType;
+  mStorageName = aStorageName;
 }
 
 JS::Value
 InterfaceToJsval(nsPIDOMWindow* aWindow, nsISupports* aObject, const nsIID* aIID)
 {
   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
   if (!sgo) {
     return JSVAL_NULL;
@@ -1085,17 +1363,19 @@ nsIFileToJsval(nsPIDOMWindow* aWindow, D
     return JSVAL_NULL;
   }
 
   if (aFile->mEditable) {
     // TODO - needs janv's file handle support.
     return JSVAL_NULL;
   }
 
-  nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(aFile->mFile, aFile->mPath,
+  nsString  compositePath;
+  aFile->GetCompositePath(compositePath);
+  nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(aFile->mFile, compositePath,
                                                 EmptyString());
   return InterfaceToJsval(aWindow, blob, &NS_GET_IID(nsIDOMBlob));
 }
 
 JS::Value StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aWindow, "Null Window");
@@ -1257,17 +1537,20 @@ ContinueCursorEvent::Continue()
   }
 
   nsDOMDeviceStorageCursor* cursor = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
   nsString cursorStorageType;
   cursor->GetStorageType(cursorStorageType);
 
   DeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, file);
   child->SetCallback(cursor);
-  DeviceStorageGetParams params(cursorStorageType, file->mRootDir, file->mPath);
+  DeviceStorageGetParams params(cursorStorageType,
+                                file->mStorageName,
+                                file->mRootDir,
+                                file->mPath);
   ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
   mRequest = nullptr;
 }
 
 NS_IMETHODIMP
 ContinueCursorEvent::Run()
 {
   nsRefPtr<DeviceStorageFile> file = GetNextFile();
@@ -1399,17 +1682,20 @@ nsDOMDeviceStorageCursor::Allow()
   if (!mFile->IsSafePath()) {
     nsCOMPtr<nsIRunnable> r = new PostErrorEvent(this, POST_ERROR_EVENT_PERMISSION_DENIED);
     NS_DispatchToMainThread(r);
     return NS_OK;
   }
 
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
     PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(this, mFile);
-    DeviceStorageEnumerationParams params(mFile->mStorageType, mFile->mRootDir, mSince);
+    DeviceStorageEnumerationParams params(mFile->mStorageType,
+                                          mFile->mStorageName,
+                                          mFile->mRootDir,
+                                          mSince);
     ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
     return NS_OK;
   }
 
   nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
   NS_ASSERTION(target, "Must have stream transport service");
 
   nsCOMPtr<InitCursorEvent> event = new InitCursorEvent(this, mFile);
@@ -1475,28 +1761,20 @@ public:
   }
 
   ~PostAvailableResultEvent() {}
 
   NS_IMETHOD Run()
   {
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-    nsString state;
-    state.Assign(NS_LITERAL_STRING("available"));
-#ifdef MOZ_WIDGET_GONK
-    nsString path;
-    nsresult rv = mFile->mFile->GetPath(path);
-    if (NS_SUCCEEDED(rv)) {
-      rv = GetSDCardStatus(path, state);
+    nsString state = NS_LITERAL_STRING("unavailable");
+    if (mFile) {
+      mFile->GetStatus(state);
     }
-    if (NS_FAILED(rv)) {
-      state.Assign(NS_LITERAL_STRING("unavailable"));
-    }
-#endif
 
     JS::Value result = StringToJsval(mRequest->GetOwner(), state);
     mRequest->FireSuccess(result);
     mRequest = nullptr;
     return NS_OK;
   }
 
 private:
@@ -1687,50 +1965,30 @@ public:
      }
 
   ~UsedSpaceFileEvent() {}
 
   NS_IMETHOD Run()
   {
     NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
-    DeviceStorageUsedSpaceCache* usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet();
-    NS_ASSERTION(usedSpaceCache, "DeviceStorageUsedSpaceCache is null");
-
-    uint64_t usedSize;
-    nsresult rv = usedSpaceCache->GetUsedSizeForType(mFile->mStorageType, &usedSize);
-    if (NS_SUCCEEDED(rv)) {
-      nsCOMPtr<nsIRunnable> r = new PostResultEvent(mRequest, usedSize);
-      NS_DispatchToMainThread(r);
-      return NS_OK;
-    }
-
     uint64_t picturesUsage = 0, videosUsage = 0, musicUsage = 0, totalUsage = 0;
-    DeviceStorageFile::DirectoryDiskUsage(mFile->mFile, &picturesUsage,
-                                          &videosUsage, &musicUsage,
-                                          &totalUsage);
+    mFile->AccumDiskUsage(&picturesUsage, &videosUsage,
+                          &musicUsage, &totalUsage);
     nsCOMPtr<nsIRunnable> r;
-
-    if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_APPS)) {
-      // Don't cache this since it's for a different volume from the media.
-      r = new PostResultEvent(mRequest, totalUsage);
+    if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
+      r = new PostResultEvent(mRequest, picturesUsage);
+    }
+    else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
+      r = new PostResultEvent(mRequest, videosUsage);
+    }
+    else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
+      r = new PostResultEvent(mRequest, musicUsage);
     } else {
-      usedSpaceCache->SetUsedSizes(picturesUsage, videosUsage, musicUsage, totalUsage);
-
-      if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
-        r = new PostResultEvent(mRequest, picturesUsage);
-      }
-      else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
-        r = new PostResultEvent(mRequest, videosUsage);
-      }
-      else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
-        r = new PostResultEvent(mRequest, musicUsage);
-      } else {
-        r = new PostResultEvent(mRequest, totalUsage);
-      }
+      r = new PostResultEvent(mRequest, totalUsage);
     }
     NS_DispatchToMainThread(r);
 
     return NS_OK;
   }
 
 private:
   nsRefPtr<DeviceStorageFile> mFile;
@@ -1746,23 +2004,23 @@ public:
       mRequest.swap(aRequest);
     }
 
   ~FreeSpaceFileEvent() {}
 
   NS_IMETHOD Run()
   {
     NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
-    nsCOMPtr<nsIRunnable> r;
+
     int64_t freeSpace = 0;
-    nsresult rv = mFile->mFile->GetDiskSpaceAvailable(&freeSpace);
-    if (NS_FAILED(rv)) {
-      freeSpace = 0;
+    if (mFile) {
+      mFile->GetDiskFreeSpace(&freeSpace);
     }
 
+    nsCOMPtr<nsIRunnable> r;
     r = new PostResultEvent(mRequest, static_cast<uint64_t>(freeSpace));
     NS_DispatchToMainThread(r);
     return NS_OK;
   }
 
 private:
   nsRefPtr<DeviceStorageFile> mFile;
   nsRefPtr<DOMRequest> mRequest;
@@ -1898,17 +2156,17 @@ public:
 
     if (!mRequest) {
       return NS_ERROR_FAILURE;
     }
 
     switch(mRequestType) {
       case DEVICE_STORAGE_REQUEST_CREATE:
       {
-        if (!mBlob) {
+        if (!mBlob || !mFile->mFile) {
           return NS_ERROR_FAILURE;
         }
 
         DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet();
         if (!typeChecker) {
           return NS_OK;
         }
 
@@ -1924,108 +2182,125 @@ public:
           BlobChild* actor = ContentChild::GetSingleton()->GetOrCreateActorForBlob(mBlob);
           if (!actor) {
             return NS_ERROR_FAILURE;
           }
 
           DeviceStorageAddParams params;
           params.blobChild() = actor;
           params.type() = mFile->mStorageType;
+          params.storageName() = mFile->mStorageName;
           params.relpath() = mFile->mPath;
 
           PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
           ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
           return NS_OK;
         }
         r = new WriteFileEvent(mBlob, mFile, mRequest);
         break;
       }
 
       case DEVICE_STORAGE_REQUEST_READ:
       case DEVICE_STORAGE_REQUEST_WRITE:
       {
+        if (!mFile->mFile) {
+          return NS_ERROR_FAILURE;
+        }
+
         DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet();
         if (!typeChecker) {
           return NS_OK;
         }
 
         if (!typeChecker->Check(mFile->mStorageType, mFile->mFile)) {
           r = new PostErrorEvent(mRequest, POST_ERROR_EVENT_ILLEGAL_TYPE);
           NS_DispatchToMainThread(r);
           return NS_OK;
         }
 
         if (XRE_GetProcessType() != GeckoProcessType_Default) {
           PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageGetParams params(mFile->mStorageType, mFile->mRootDir, mFile->mPath);
+          DeviceStorageGetParams params(mFile->mStorageType,
+                                        mFile->mStorageName,
+                                        mFile->mRootDir,
+                                        mFile->mPath);
           ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
           return NS_OK;
         }
 
         r = new ReadFileEvent(mFile, mRequest);
         break;
       }
 
       case DEVICE_STORAGE_REQUEST_DELETE:
       {
+        if (!mFile->mFile) {
+          return NS_ERROR_FAILURE;
+        }
+
         DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet();
         if (!typeChecker) {
           return NS_OK;
         }
 
         if (!typeChecker->Check(mFile->mStorageType, mFile->mFile)) {
           r = new PostErrorEvent(mRequest, POST_ERROR_EVENT_ILLEGAL_TYPE);
           NS_DispatchToMainThread(r);
           return NS_OK;
         }
 
         if (XRE_GetProcessType() != GeckoProcessType_Default) {
           PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageDeleteParams params(mFile->mStorageType, mFile->mPath);
+          DeviceStorageDeleteParams params(mFile->mStorageType,
+                                           mFile->mStorageName,
+                                           mFile->mPath);
           ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
           return NS_OK;
         }
         r = new DeleteFileEvent(mFile, mRequest);
         break;
       }
 
       case DEVICE_STORAGE_REQUEST_FREE_SPACE:
       {
         if (XRE_GetProcessType() != GeckoProcessType_Default) {
           PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageFreeSpaceParams params(mFile->mStorageType, mFile->mPath);
+          DeviceStorageFreeSpaceParams params(mFile->mStorageType,
+                                              mFile->mStorageName);
           ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
           return NS_OK;
         }
         r = new FreeSpaceFileEvent(mFile, mRequest);
         break;
       }
 
       case DEVICE_STORAGE_REQUEST_USED_SPACE:
       {
         if (XRE_GetProcessType() != GeckoProcessType_Default) {
           PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageUsedSpaceParams params(mFile->mStorageType, mFile->mPath);
+          DeviceStorageUsedSpaceParams params(mFile->mStorageType,
+                                              mFile->mStorageName);
           ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
           return NS_OK;
         }
         // this needs to be dispatched to only one (1)
         // thread or we will do more work than required.
         DeviceStorageUsedSpaceCache* usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet();
         NS_ASSERTION(usedSpaceCache, "DeviceStorageUsedSpaceCache is null");
         r = new UsedSpaceFileEvent(mFile, mRequest);
         usedSpaceCache->Dispatch(r);
         return NS_OK;
       }
 
       case DEVICE_STORAGE_REQUEST_AVAILABLE:
       {
         if (XRE_GetProcessType() != GeckoProcessType_Default) {
           PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageAvailableParams params(mFile->mStorageType, mFile->mPath);
+          DeviceStorageAvailableParams params(mFile->mStorageType,
+                                              mFile->mStorageName);
           ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
           return NS_OK;
         }
         r = new PostAvailableResultEvent(mFile, mRequest);
         NS_DispatchToMainThread(r);
         return NS_OK;
       }
 
@@ -2100,26 +2375,41 @@ NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStor
 NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorage, nsDOMEventTargetHelper)
 
 nsDOMDeviceStorage::nsDOMDeviceStorage()
   : mIsWatchingFile(false)
   , mAllowedToWatchFile(false)
 { }
 
 nsresult
+nsDOMDeviceStorage::Init(nsPIDOMWindow* aWindow, const nsAString &aType,
+                         nsTArray<nsRefPtr<nsDOMDeviceStorage> > &aStores)
+{
+  mStores.AppendElements(aStores);
+  nsresult rv = Init(aWindow, aType, NS_LITERAL_STRING(""));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
 nsDOMDeviceStorage::Init(nsPIDOMWindow* aWindow, const nsAString &aType, const nsAString &aVolName)
 {
   DebugOnly<FileUpdateDispatcher*> observer = FileUpdateDispatcher::GetSingleton();
   NS_ASSERTION(observer, "FileUpdateDispatcher is null");
 
   NS_ASSERTION(aWindow, "Must have a content dom");
 
-  SetRootDirectoryForType(aType, aVolName);
-  if (!mRootDirectory) {
-    return NS_ERROR_NOT_AVAILABLE;
+  if (IsComposite()) {
+    mStorageType = aType;
+  } else {
+    SetRootDirectoryForType(aType, aVolName);
+    if (!mRootDirectory) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
   }
 
   BindToOwner(aWindow);
 
   // Grab the principal of the document
   nsCOMPtr<nsIDOMDocument> domdoc;
   aWindow->GetDocument(getter_AddRefs(domdoc));
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
@@ -2160,73 +2450,223 @@ nsDOMDeviceStorage::Shutdown()
   UnregisterForSDCardChanges(this);
 #endif
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   obs->RemoveObserver(this, "file-watcher-update");
   obs->RemoveObserver(this, "disk-space-watcher");
 }
 
+StaticRefPtr<nsDOMDeviceStorage::VolumeNameCache> nsDOMDeviceStorage::sVolumeNameCache;
+
+// static
 void
-nsDOMDeviceStorage::GetOrderedVolumeNames(const nsAString &aType,
-                                          nsTArray<nsString> &aVolumeNames)
+nsDOMDeviceStorage::GetOrderedVolumeNames(nsDOMDeviceStorage::VolumeNameArray &aVolumeNames)
 {
+  if (sVolumeNameCache && sVolumeNameCache->mVolumeNames.Length() > 0) {
+    aVolumeNames.AppendElements(sVolumeNameCache->mVolumeNames);
+    return;
+  }
 #ifdef MOZ_WIDGET_GONK
-  if (DeviceStorageTypeChecker::IsVolumeBased(aType)) {
-    nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
-    if (vs) {
-      vs->GetVolumeNames(aVolumeNames);
-
-      // If the volume sdcard exists, then we want it to be first.
-
-      nsTArray<nsString>::index_type sdcardIndex;
-      sdcardIndex = aVolumeNames.IndexOf(NS_LITERAL_STRING("sdcard"));
-      if ((sdcardIndex != nsTArray<nsString>::NoIndex)
-      &&  (sdcardIndex > 0)) {
-        aVolumeNames.RemoveElementAt(sdcardIndex);
-        aVolumeNames.InsertElementAt(0, NS_LITERAL_STRING("sdcard"));
-      }
+  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
+  if (vs) {
+    vs->GetVolumeNames(aVolumeNames);
+
+    // 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.Length() == 0) {
     aVolumeNames.AppendElement(NS_LITERAL_STRING(""));
   }
+  sVolumeNameCache = new VolumeNameCache;
+  sVolumeNameCache->mVolumeNames.AppendElements(aVolumeNames);
 }
 
+// static
+void
+nsDOMDeviceStorage::CreateDeviceStorageFor(nsPIDOMWindow* aWin,
+                                           const nsAString &aType,
+                                           nsDOMDeviceStorage** aStore)
+{
+  // Create the underlying non-composite device storage objects
+  nsTArray<nsRefPtr<nsDOMDeviceStorage> > stores;
+  CreateDeviceStoragesFor(aWin, aType, stores);
+  if (stores.Length() == 0) {
+    *aStore = nullptr;
+    return;
+  }
+
+  if (!DeviceStorageTypeChecker::IsVolumeBased(aType)) {
+    // Since the storage type isn't volume based, don't bother creating
+    // a composite object. Just use the one we got.
+    NS_ASSERTION(stores.Length() == 1, "Only expecting a single storage object");
+    NS_ADDREF(*aStore = stores[0].get());
+    return;
+  }
+
+  // Create the composite device storage object
+  nsRefPtr<nsDOMDeviceStorage> composite = new nsDOMDeviceStorage();
+  if (NS_FAILED(composite->Init(aWin, aType, stores))) {
+    *aStore = nullptr;
+    return;
+  }
+  NS_ADDREF(*aStore = composite.get());
+}
+
+// static
 void
 nsDOMDeviceStorage::CreateDeviceStoragesFor(nsPIDOMWindow* aWin,
                                             const nsAString &aType,
                                             nsTArray<nsRefPtr<nsDOMDeviceStorage> > &aStores)
 {
   nsresult rv;
 
   if (!DeviceStorageTypeChecker::IsVolumeBased(aType)) {
     nsRefPtr<nsDOMDeviceStorage> storage = new nsDOMDeviceStorage();
     rv = storage->Init(aWin, aType, NS_LITERAL_STRING(""));
     if (NS_SUCCEEDED(rv)) {
       aStores.AppendElement(storage);
-    } else {
     }
     return;
   }
-  nsTArray<nsString>  volNames;
-  GetOrderedVolumeNames(aType, volNames);
-
-  nsTArray<nsString>::size_type numVolumeNames = volNames.Length();
-  for (nsTArray<nsString>::index_type i = 0; i < numVolumeNames; i++) {
+  VolumeNameArray volNames;
+  GetOrderedVolumeNames(volNames);
+
+  VolumeNameArray::size_type numVolumeNames = volNames.Length();
+  for (VolumeNameArray::index_type i = 0; i < numVolumeNames; i++) {
     nsRefPtr<nsDOMDeviceStorage> storage = new nsDOMDeviceStorage();
     rv = storage->Init(aWin, aType, volNames[i]);
     if (NS_FAILED(rv)) {
       break;
     }
     aStores.AppendElement(storage);
   }
 }
 
+// static
+bool
+nsDOMDeviceStorage::ParseCompositePath(const nsAString& aCompositePath,
+                                       nsAString& aOutStorageName,
+                                       nsAString& aOutStoragePath)
+{
+  aOutStorageName.AssignLiteral("");
+  aOutStoragePath.AssignLiteral("");
+
+  NS_NAMED_LITERAL_STRING(slash, "/");
+
+  nsDependentSubstring storageName;
+
+  if (StringBeginsWith(aCompositePath, slash)) {
+    int32_t slashIndex = aCompositePath.FindChar('/', 1);
+    if (slashIndex == kNotFound) {
+      // names of the form /filename are illegal
+      return false;
+    }
+    storageName.Rebind(aCompositePath, 1, slashIndex - 1);
+    aOutStoragePath = Substring(aCompositePath, slashIndex + 1);
+  } else {
+    aOutStoragePath = aCompositePath;
+  }
+
+  if (!storageName.EqualsLiteral("")) {
+    aOutStorageName = storageName;
+    return true;
+  }
+
+  DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet();
+  NS_ASSERTION(typeChecker, "DeviceStorageTypeChecker is null");
+  nsString storageType;
+  typeChecker->GetTypeFromFileName(aOutStoragePath, storageType);
+
+  nsString defStorageName;
+  GetWritableStorageName(storageType, defStorageName);
+  if (defStorageName.EqualsLiteral("")) {
+    return false;
+  }
+  aOutStorageName = defStorageName;
+  return true;
+}
+
+already_AddRefed<nsDOMDeviceStorage>
+nsDOMDeviceStorage::GetStorage(const nsAString& aCompositePath, nsAString& aOutStoragePath)
+{
+  MOZ_ASSERT(IsComposite());
+
+  nsString storageName;
+  if (!ParseCompositePath(aCompositePath, storageName, aOutStoragePath)) {
+    return nullptr;
+  }
+
+  nsRefPtr<nsDOMDeviceStorage> ds;
+  ds = GetStorageByName(storageName);
+  return ds.forget();
+}
+
+already_AddRefed<nsDOMDeviceStorage>
+nsDOMDeviceStorage::GetStorageByName(const nsAString& aStorageName)
+{
+  nsTArray<nsRefPtr<nsDOMDeviceStorage> >::size_type n = mStores.Length();
+  nsTArray<nsRefPtr<nsDOMDeviceStorage> >::index_type i;
+  for (i = 0; i < n; i++) {
+    nsRefPtr<nsDOMDeviceStorage> ds = mStores[i];
+    if (ds->mStorageName == aStorageName) {
+      return ds.forget();
+    }
+  }
+  return nullptr;
+}
+
+// static
+void
+nsDOMDeviceStorage::GetWritableStorageName(const nsAString& aStorageType,
+                                           nsAString& aStorageName)
+{
+  // See if the preferred volume is available.
+  nsRefPtr<nsDOMDeviceStorage> ds;
+  nsAdoptingString prefStorageName =
+    mozilla::Preferences::GetString("device.storage.writable.name");
+  if (prefStorageName) {
+    DeviceStorageFile dsf(aStorageType, prefStorageName);
+    if (dsf.IsAvailable()) {
+      aStorageName = prefStorageName;
+      return;
+    }
+  }
+
+  // No preferred storage, or the preferred storage is unavailable. Find
+  // the first available storage area.
+
+  VolumeNameArray volNames;
+  GetOrderedVolumeNames(volNames);
+  VolumeNameArray::size_type numVolumes = volNames.Length();
+  VolumeNameArray::index_type i;
+  for (i = 0; i < numVolumes; i++) {
+    DeviceStorageFile dsf(aStorageType, volNames[i]);
+    if (dsf.IsAvailable()) {
+      aStorageName = volNames[i];
+      return;
+    }
+  }
+}
+
+bool
+nsDOMDeviceStorage::IsAvailable()
+{
+  DeviceStorageFile dsf(mStorageType, mStorageName);
+  return dsf.IsAvailable();
+}
+
 NS_IMETHODIMP
 nsDOMDeviceStorage::Add(nsIDOMBlob *aBlob, nsIDOMDOMRequest * *_retval)
 {
   if (!aBlob) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
@@ -2242,18 +2682,18 @@ nsDOMDeviceStorage::Add(nsIDOMBlob *aBlo
 
   nsCString extension;
   mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType), EmptyCString(), extension);
   // if extension is null here, we will ignore it for now.
   // AddNamed() will check the file path and fail.  This
   // will post an onerror to the requestee.
 
   // possible race here w/ unique filename
-  char buffer[128];
-  NS_MakeRandomString(buffer, ArrayLength(buffer));
+  char buffer[32];
+  NS_MakeRandomString(buffer, ArrayLength(buffer) - 1);
 
   nsAutoCString path;
   path.Assign(nsDependentCString(buffer));
   path.Append(".");
   path.Append(extension);
 
   return AddNamed(aBlob, NS_ConvertASCIItoUTF16(path), _retval);
 }
@@ -2272,22 +2712,36 @@ nsDOMDeviceStorage::AddNamed(nsIDOMBlob 
     return NS_ERROR_UNEXPECTED;
   }
 
   DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet();
   if (!typeChecker) {
     return NS_ERROR_FAILURE;
   }
 
+  nsCOMPtr<nsIRunnable> r;
+
+  if (IsComposite()) {
+    nsString storagePath;
+    nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
+    if (!ds) {
+      nsRefPtr<DOMRequest> request = new DOMRequest(win);
+      NS_ADDREF(*_retval = request);
+      r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
+      NS_DispatchToMainThread(r);
+    }
+    return ds->AddNamed(aBlob, storagePath, _retval);
+  }
+
   nsRefPtr<DOMRequest> request = new DOMRequest(win);
   NS_ADDREF(*_retval = request);
 
-  nsCOMPtr<nsIRunnable> r;
-
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, aPath);
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName,
+                                                          aPath);
   if (!dsf->IsSafePath()) {
     r = new PostErrorEvent(request, POST_ERROR_EVENT_PERMISSION_DENIED);
   } else if (!typeChecker->Check(mStorageType, dsf->mFile) ||
       !typeChecker->Check(mStorageType, aBlob)) {
     r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_TYPE);
   }
   else {
     r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_CREATE,
@@ -2333,23 +2787,45 @@ nsDOMDeviceStorage::GetInternal(const JS
   JSString* jsstr = JS_ValueToString(aCx, aPath);
   nsDependentJSString path;
   if (!path.init(aCx, jsstr)) {
     r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
     NS_DispatchToMainThread(r);
     return NS_OK;
   }
 
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, path);
+  if (IsComposite()) {
+    nsString storagePath;
+    nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(path, storagePath);
+    if (!ds) {
+      r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
+      NS_DispatchToMainThread(r);
+      return NS_OK;
+    }
+    return ds->GetInternal(win, storagePath, request.get(), aEditable);
+  }
+  return GetInternal(win, path, request.get(), aEditable);
+}
+
+nsresult
+nsDOMDeviceStorage::GetInternal(nsPIDOMWindow *aWin,
+                                const nsAString& aPath,
+                                DOMRequest* aRequest,
+                                bool aEditable)
+{
+  nsCOMPtr<nsIRunnable> r;
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName,
+                                                          aPath);
   dsf->SetEditable(aEditable);
   if (!dsf->IsSafePath()) {
-    r = new PostErrorEvent(request, POST_ERROR_EVENT_PERMISSION_DENIED);
+    r = new PostErrorEvent(aRequest, POST_ERROR_EVENT_PERMISSION_DENIED);
   } else {
     r = new DeviceStorageRequest(aEditable ? DEVICE_STORAGE_REQUEST_WRITE : DEVICE_STORAGE_REQUEST_READ,
-                                 win, mPrincipal, dsf, request);
+                                 aWin, mPrincipal, dsf, aRequest);
   }
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMDeviceStorage::Delete(const JS::Value & aPath, JSContext* aCx, nsIDOMDOMRequest * *_retval)
 {
@@ -2366,41 +2842,62 @@ nsDOMDeviceStorage::Delete(const JS::Val
   JSString* jsstr = JS_ValueToString(aCx, aPath);
   nsDependentJSString path;
   if (!path.init(aCx, jsstr)) {
     r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
     NS_DispatchToMainThread(r);
     return NS_OK;
   }
 
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, path);
-
+  if (IsComposite()) {
+    nsString storagePath;
+    nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(path, storagePath);
+    if (!ds) {
+      r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
+      NS_DispatchToMainThread(r);
+      return NS_OK;
+    }
+    return ds->DeleteInternal(win, storagePath, request.get());
+  }
+  return DeleteInternal(win, path, request.get());
+}
+
+nsresult
+nsDOMDeviceStorage::DeleteInternal(nsPIDOMWindow *aWin,
+                    const nsAString& aPath,
+                    mozilla::dom::DOMRequest* aRequest)
+{
+  nsCOMPtr<nsIRunnable> r;
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName,
+                                                          aPath);
   if (!dsf->IsSafePath()) {
-    r = new PostErrorEvent(request, POST_ERROR_EVENT_PERMISSION_DENIED);
+    r = new PostErrorEvent(aRequest, POST_ERROR_EVENT_PERMISSION_DENIED);
   }
   else {
     r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_DELETE,
-                                 win, mPrincipal, dsf, request);
+                                 aWin, mPrincipal, dsf, aRequest);
   }
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMDeviceStorage::FreeSpace(nsIDOMDOMRequest** aRetval)
 {
   nsCOMPtr<nsPIDOMWindow> win = GetOwner();
   if (!win) {
     return NS_ERROR_UNEXPECTED;
   }
 
   nsRefPtr<DOMRequest> request = new DOMRequest(win);
   NS_ADDREF(*aRetval = request);
 
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType);
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName);
   nsCOMPtr<nsIRunnable> r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_FREE_SPACE,
                                                      win,
                                                      mPrincipal,
                                                      dsf,
                                                      request);
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
@@ -2414,17 +2911,18 @@ nsDOMDeviceStorage::UsedSpace(nsIDOMDOMR
   nsCOMPtr<nsPIDOMWindow> win = GetOwner();
   if (!win) {
     return NS_ERROR_UNEXPECTED;
   }
 
   nsRefPtr<DOMRequest> request = new DOMRequest(win);
   NS_ADDREF(*aRetval = request);
 
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType);
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName);
   nsCOMPtr<nsIRunnable> r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_USED_SPACE,
                                                      win,
                                                      mPrincipal,
                                                      dsf,
                                                      request);
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
@@ -2435,17 +2933,18 @@ nsDOMDeviceStorage::Available(nsIDOMDOMR
   nsCOMPtr<nsPIDOMWindow> win = GetOwner();
   if (!win) {
     return NS_ERROR_UNEXPECTED;
   }
 
   nsRefPtr<DOMRequest> request = new DOMRequest(win);
   NS_ADDREF(*aRetval = request);
 
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType);
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName);
   nsCOMPtr<nsIRunnable> r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_AVAILABLE,
                                                      win,
                                                      mPrincipal,
                                                      dsf,
                                                      request);
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
@@ -2455,19 +2954,19 @@ nsDOMDeviceStorage::GetRootDirectory(nsI
 {
   if (!mRootDirectory) {
     return NS_ERROR_FAILURE;
   }
   return mRootDirectory->Clone(aRootDirectory);
 }
 
 NS_IMETHODIMP
-nsDOMDeviceStorage::GetVolumeName(nsAString & aVolumeName)
+nsDOMDeviceStorage::GetStorageName(nsAString& aStorageName)
 {
-  aVolumeName = mVolumeName;
+  aStorageName = mStorageName;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMDeviceStorage::Enumerate(const JS::Value & aName,
                              const JS::Value & aOptions,
                              JSContext* aCx,
                              uint8_t aArgc,
@@ -2535,17 +3034,20 @@ nsDOMDeviceStorage::EnumerateInternal(co
     }
 
     if (aArgc == 2 && (JSVAL_IS_VOID(aOptions) || aOptions.isNull() || !aOptions.isObject())) {
       return NS_ERROR_FAILURE;
     }
     since = ExtractDateFromOptions(aCx, aOptions);
   }
 
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, path, NS_LITERAL_STRING(""));
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName,
+                                                          path,
+                                                          NS_LITERAL_STRING(""));
   dsf->SetEditable(aEditable);
 
   nsRefPtr<nsDOMDeviceStorageCursor> cursor = new nsDOMDeviceStorageCursor(win, mPrincipal,
                                                                            dsf, since);
   nsRefPtr<DeviceStorageCursorRequest> r = new DeviceStorageCursorRequest(cursor);
 
   NS_ADDREF(*aRetval = cursor);
 
@@ -2582,26 +3084,27 @@ nsDOMDeviceStorage::EnumerateInternal(co
     prompt->Prompt(r);
   }
 
   return NS_OK;
 }
 
 #ifdef MOZ_WIDGET_GONK
 void
-nsDOMDeviceStorage::DispatchMountChangeEvent(nsAString& aType)
+nsDOMDeviceStorage::DispatchMountChangeEvent(nsAString& aVolumeName,
+                                             nsAString& aVolumeStatus)
 {
   nsCOMPtr<nsIDOMEvent> event;
   NS_NewDOMDeviceStorageChangeEvent(getter_AddRefs(event), this, nullptr, nullptr);
 
   nsCOMPtr<nsIDOMDeviceStorageChangeEvent> ce = do_QueryInterface(event);
   nsresult rv = ce->InitDeviceStorageChangeEvent(NS_LITERAL_STRING("change"),
                                                  true, false,
-                                                 NS_LITERAL_STRING(""),
-                                                 aType);
+                                                 aVolumeName,
+                                                 aVolumeStatus);
   if (NS_FAILED(rv)) {
     return;
   }
 
   bool ignore;
   DispatchEvent(ce, &ignore);
 }
 #endif
@@ -2611,42 +3114,38 @@ nsDOMDeviceStorage::Observe(nsISupports 
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (!strcmp(aTopic, "file-watcher-update")) {
 
     DeviceStorageFile* file = static_cast<DeviceStorageFile*>(aSubject);
     Notify(NS_ConvertUTF16toUTF8(aData).get(), file);
     return NS_OK;
-  } else if (!strcmp(aTopic, "disk-space-watcher")) {
+  }
+  if (!strcmp(aTopic, "disk-space-watcher")) {
     // 'disk-space-watcher' notifications are sent when there is a modification
     // of a file in a specific location while a low device storage situation
     // exists or after recovery of a low storage situation. For Firefox OS,
     // these notifications are specific for apps storage.
-    nsRefPtr<DeviceStorageFile> file = new DeviceStorageFile(mStorageType);
+    nsRefPtr<DeviceStorageFile> file =
+      new DeviceStorageFile(mStorageType, mStorageName);
     if (!strcmp(NS_ConvertUTF16toUTF8(aData).get(), "full")) {
       Notify("low-disk-space", file);
     } else if (!strcmp(NS_ConvertUTF16toUTF8(aData).get(), "free")) {
       Notify("available-disk-space", file);
     }
     return NS_OK;
   }
 
 #ifdef MOZ_WIDGET_GONK
   else if (!strcmp(aTopic, NS_VOLUME_STATE_CHANGED)) {
     nsCOMPtr<nsIVolume> vol = do_QueryInterface(aSubject);
     if (!vol) {
       return NS_OK;
     }
-    nsString volName;
-    vol->GetName(volName);
-    if (!volName.EqualsLiteral("sdcard")) {
-      return NS_OK;
-    }
-
     int32_t state;
     nsresult rv = vol->GetState(&state);
     if (NS_FAILED(rv)) {
       return NS_OK;
     }
 
     nsString type;
     if (state == nsIVolume::STATE_MOUNTED) {
@@ -2654,70 +3153,57 @@ nsDOMDeviceStorage::Observe(nsISupports 
     } else if (state == nsIVolume::STATE_SHARED || state == nsIVolume::STATE_SHAREDMNT) {
       type.Assign(NS_LITERAL_STRING("shared"));
     } else if (state == nsIVolume::STATE_NOMEDIA || state == nsIVolume::STATE_UNMOUNTING) {
       type.Assign(NS_LITERAL_STRING("unavailable"));
     } else {
       // ignore anything else.
       return NS_OK;
     }
+    nsString volName;
+    vol->GetName(volName);
 
     DeviceStorageUsedSpaceCache* usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet();
     NS_ASSERTION(usedSpaceCache, "DeviceStorageUsedSpaceCache is null");
-    usedSpaceCache->Invalidate();
-
-    DispatchMountChangeEvent(type);
+    usedSpaceCache->Invalidate(volName);
+
+    DispatchMountChangeEvent(volName, type);
     return NS_OK;
   }
 #endif
   return NS_OK;
 }
 
 nsresult
 nsDOMDeviceStorage::Notify(const char* aReason, DeviceStorageFile* aFile)
 {
   if (!mAllowedToWatchFile) {
     return NS_OK;
   }
 
-  if (!mStorageType.Equals(aFile->mStorageType)) {
+  if (!mStorageType.Equals(aFile->mStorageType) ||
+      !mStorageName.Equals(aFile->mStorageName)) {
     // Ignore this
     return NS_OK;
   }
 
-  if (!mRootDirectory) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsString rootpath;
-  nsresult rv = mRootDirectory->GetPath(rootpath);
-  if (NS_FAILED(rv)) {
-    return NS_OK;
-  }
-
-  nsString fullpath;
-  rv = aFile->mFile->GetPath(fullpath);
-  if (NS_FAILED(rv)) {
-    return NS_OK;
-  }
-
-  NS_ASSERTION(fullpath.Length() >= rootpath.Length(), "Root path longer than full path!");
-
-  nsAString::size_type len = rootpath.Length() + 1; // +1 for the trailing /
-  nsDependentSubstring newPath (fullpath, len, fullpath.Length() - len);
-
   nsCOMPtr<nsIDOMEvent> event;
   NS_NewDOMDeviceStorageChangeEvent(getter_AddRefs(event), this,
                                     nullptr, nullptr);
 
   nsCOMPtr<nsIDOMDeviceStorageChangeEvent> ce = do_QueryInterface(event);
 
   nsString reason;
   reason.AssignWithConversion(aReason);
-  rv = ce->InitDeviceStorageChangeEvent(NS_LITERAL_STRING("change"), true, false, newPath, reason);
+
+  nsString compositePath;
+  aFile->GetCompositePath(compositePath);
+  nsresult rv = ce->InitDeviceStorageChangeEvent(NS_LITERAL_STRING("change"),
+                                                 true, false, compositePath,
+                                                 reason);
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool ignore;
   DispatchEvent(ce, &ignore);
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -2726,19 +3212,29 @@ nsDOMDeviceStorage::AddEventListener(con
                                      bool aUseCapture,
                                      bool aWantsUntrusted,
                                      uint8_t aArgc)
 {
   nsCOMPtr<nsPIDOMWindow> win = GetOwner();
   if (!win) {
     return NS_ERROR_UNEXPECTED;
   }
+  if (IsComposite()) {
+    nsTArray<nsRefPtr<nsDOMDeviceStorage> >::size_type n = mStores.Length();
+    nsTArray<nsRefPtr<nsDOMDeviceStorage> >::index_type i;
+    for (i = 0; i < n; i++) {
+      nsresult rv = mStores[i]->AddEventListener(aType, aListener, aUseCapture, aWantsUntrusted, aArgc);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    return NS_OK;
+  }
 
   nsRefPtr<DOMRequest> request = new DOMRequest(win);
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType);
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName);
   nsCOMPtr<nsIRunnable> r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_WATCH,
                                                      win, mPrincipal, dsf, request, this);
   NS_DispatchToMainThread(r);
   return nsDOMEventTargetHelper::AddEventListener(aType, aListener, aUseCapture, aWantsUntrusted, aArgc);
 }
 
 void
 nsDOMDeviceStorage::AddEventListener(const nsAString & aType,
@@ -2747,62 +3243,97 @@ nsDOMDeviceStorage::AddEventListener(con
                                      const Nullable<bool>& aWantsUntrusted,
                                      ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindow> win = GetOwner();
   if (!win) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
+  if (IsComposite()) {
+    nsTArray<nsRefPtr<nsDOMDeviceStorage> >::size_type n = mStores.Length();
+    nsTArray<nsRefPtr<nsDOMDeviceStorage> >::index_type i;
+    for (i = 0; i < n; i++) {
+      mStores[i]->AddEventListener(aType, aListener, aUseCapture, aWantsUntrusted, aRv);
+    }
+    return;
+  }
 
   nsRefPtr<DOMRequest> request = new DOMRequest(win);
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType);
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName);
   nsCOMPtr<nsIRunnable> r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_WATCH,
                                                      win, mPrincipal, dsf, request, this);
   NS_DispatchToMainThread(r);
   nsDOMEventTargetHelper::AddEventListener(aType, aListener, aUseCapture, aWantsUntrusted, aRv);
 }
 
 NS_IMETHODIMP
 nsDOMDeviceStorage::AddSystemEventListener(const nsAString & aType,
                                            nsIDOMEventListener *aListener,
                                            bool aUseCapture,
                                            bool aWantsUntrusted,
                                            uint8_t aArgc)
 {
+  if (IsComposite()) {
+    nsTArray<nsRefPtr<nsDOMDeviceStorage> >::size_type n = mStores.Length();
+    nsTArray<nsRefPtr<nsDOMDeviceStorage> >::index_type i;
+    for (i = 0; i < n; i++) {
+      nsresult rv = mStores[i]->AddSystemEventListener(aType, aListener, aUseCapture, aWantsUntrusted, aArgc);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    return NS_OK;
+  }
   if (!mIsWatchingFile) {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->AddObserver(this, "file-watcher-update", false);
     mIsWatchingFile = true;
   }
 
   return nsDOMDeviceStorage::AddEventListener(aType,aListener,aUseCapture,aWantsUntrusted, aArgc);
 }
 
 NS_IMETHODIMP
 nsDOMDeviceStorage::RemoveEventListener(const nsAString & aType,
                                         nsIDOMEventListener *aListener,
                                         bool aUseCapture)
 {
+  if (IsComposite()) {
+    nsTArray<nsRefPtr<nsDOMDeviceStorage> >::size_type n = mStores.Length();
+    nsTArray<nsRefPtr<nsDOMDeviceStorage> >::index_type i;
+    for (i = 0; i < n; i++) {
+      nsresult rv = mStores[i]->RemoveEventListener(aType, aListener, aUseCapture);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    return NS_OK;
+  }
   nsDOMEventTargetHelper::RemoveEventListener(aType, aListener, false);
 
   if (mIsWatchingFile && !HasListenersFor(nsGkAtoms::onchange)) {
     mIsWatchingFile = false;
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->RemoveObserver(this, "file-watcher-update");
   }
   return NS_OK;
 }
 
 void
 nsDOMDeviceStorage::RemoveEventListener(const nsAString& aType,
                                         nsIDOMEventListener* aListener,
                                         bool aCapture,
                                         ErrorResult& aRv)
 {
+  if (IsComposite()) {
+    nsTArray<nsRefPtr<nsDOMDeviceStorage> >::size_type n = mStores.Length();
+    nsTArray<nsRefPtr<nsDOMDeviceStorage> >::index_type i;
+    for (i = 0; i < n; i++) {
+      mStores[i]->RemoveEventListener(aType, aListener, aCapture, aRv);
+    }
+    return;
+  }
   nsDOMEventTargetHelper::RemoveEventListener(aType, aListener, aCapture, aRv);
 
   if (mIsWatchingFile && !HasListenersFor(nsGkAtoms::onchange)) {
     mIsWatchingFile = false;
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->RemoveObserver(this, "file-watcher-update");
   }
 }
--- a/dom/devicestorage/nsDeviceStorage.h
+++ b/dom/devicestorage/nsDeviceStorage.h
@@ -23,16 +23,17 @@ class nsPIDOMWindow;
 #include "nsWeakPtr.h"
 #include "nsIDOMEventListener.h"
 #include "nsIObserver.h"
 #include "nsIStringBundle.h"
 #include "mozilla/Mutex.h"
 #include "prtime.h"
 #include "DeviceStorage.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestChild.h"
+#include "mozilla/RefPtr.h"
 #include "mozilla/StaticPtr.h"
 
 namespace mozilla {
 class ErrorResult;
 } // namespace mozilla
 
 #define POST_ERROR_EVENT_FILE_EXISTS                 "NoModificationAllowedError"
 #define POST_ERROR_EVENT_FILE_DOES_NOT_EXIST         "NotFoundError"
@@ -59,62 +60,81 @@ public:
 
   DeviceStorageUsedSpaceCache();
   ~DeviceStorageUsedSpaceCache();
 
 
   class InvalidateRunnable MOZ_FINAL : public nsRunnable
   {
     public:
-      InvalidateRunnable(DeviceStorageUsedSpaceCache* aCache) {
-        mOwner = aCache;
-      }
+      InvalidateRunnable(DeviceStorageUsedSpaceCache* aCache, 
+                         const nsAString& aStorageName)
+        : mCache(aCache)
+        , mStorageName(aStorageName) {}
 
       ~InvalidateRunnable() {}
 
-      NS_IMETHOD Run() {
-        mOwner->mDirty = true;
+      NS_IMETHOD Run()
+      {
+        mozilla::RefPtr<DeviceStorageUsedSpaceCache::CacheEntry> cacheEntry;
+        cacheEntry = mCache->GetCacheEntry(mStorageName);
+        if (cacheEntry) {
+          cacheEntry->mDirty = true;
+        }
         return NS_OK;
       }
     private:
-      DeviceStorageUsedSpaceCache* mOwner;
+      DeviceStorageUsedSpaceCache* mCache;
+      nsString mStorageName;
   };
 
-  void Invalidate() {
+  void Invalidate(const nsAString& aStorageName)
+  {
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
     NS_ASSERTION(mIOThread, "Null mIOThread!");
 
-    nsRefPtr<InvalidateRunnable> r = new InvalidateRunnable(this);
+    nsRefPtr<InvalidateRunnable> r = new InvalidateRunnable(this, aStorageName);
     mIOThread->Dispatch(r, NS_DISPATCH_NORMAL);
   }
 
-  void Dispatch(nsIRunnable* aRunnable) {
+  void Dispatch(nsIRunnable* aRunnable)
+  {
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
     NS_ASSERTION(mIOThread, "Null mIOThread!");
 
     mIOThread->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
   }
 
-  nsresult GetUsedSizeForType(const nsAString& aStorageType, uint64_t* usedSize);
-  void SetUsedSizes(uint64_t aPictureSize, uint64_t aVideosSize,
+  nsresult GetUsedSizeForType(const nsAString& aStorageType,
+                              const nsAString& aStorageName,
+                              uint64_t* usedSize);
+  nsresult AccumUsedSizes(const nsAString& aStorageName,
+                          uint64_t* aPictureSize, uint64_t* aVideosSize,
+                          uint64_t* aMusicSize, uint64_t* aTotalSize);
+
+  void SetUsedSizes(const nsAString& aStorageName,
+                    uint64_t aPictureSize, uint64_t aVideosSize,
                     uint64_t aMusicSize, uint64_t aTotalSize);
 
 private:
   friend class InvalidateRunnable;
 
-  nsresult GetPicturesUsedSize(uint64_t* usedSize);
-  nsresult GetMusicUsedSize(uint64_t* usedSize);
-  nsresult GetVideosUsedSize(uint64_t* usedSize);
-  nsresult GetTotalUsedSize(uint64_t* usedSize);
+  class CacheEntry : public mozilla::RefCounted<CacheEntry> 
+  {
+  public:
+    bool mDirty;
+    nsString mStorageName;
+    uint64_t mPicturesUsedSize;
+    uint64_t mVideosUsedSize;
+    uint64_t mMusicUsedSize;
+    uint64_t mTotalUsedSize;
+  };
+  mozilla::TemporaryRef<CacheEntry> GetCacheEntry(const nsAString& aStorageName);
 
-  bool mDirty;
-  uint64_t mPicturesUsedSize;
-  uint64_t mVideosUsedSize;
-  uint64_t mMusicUsedSize;
-  uint64_t mTotalUsedSize;
+  nsTArray<mozilla::RefPtr<CacheEntry> > mCacheEntries;
 
   nsCOMPtr<nsIThread> mIOThread;
 
   static mozilla::StaticAutoPtr<DeviceStorageUsedSpaceCache> sDeviceStorageUsedSpaceCache;
 };
 
 class DeviceStorageTypeChecker MOZ_FINAL
 {
@@ -124,16 +144,17 @@ public:
   DeviceStorageTypeChecker();
   ~DeviceStorageTypeChecker();
 
   void InitFromBundle(nsIStringBundle* aBundle);
 
   bool Check(const nsAString& aType, nsIDOMBlob* aBlob);
   bool Check(const nsAString& aType, nsIFile* aFile);
   void GetTypeFromFile(nsIFile* aFile, nsAString& aType);
+  void GetTypeFromFileName(const nsAString& aFileName, nsAString& aType);
 
   static nsresult GetPermissionForType(const nsAString& aType, nsACString& aPermissionResult);
   static nsresult GetAccessForRequest(const DeviceStorageRequestType aRequestType, nsACString& aAccessResult);
   static bool IsVolumeBased(const nsAString& aType);
 
 private:
   nsString mPicturesExtensions;
   nsString mVideosExtensions;
@@ -199,13 +220,9 @@ JS::Value
 StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString);
 
 JS::Value
 nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile);
 
 JS::Value
 InterfaceToJsval(nsPIDOMWindow* aWindow, nsISupports* aObject, const nsIID* aIID);
 
-#ifdef MOZ_WIDGET_GONK
-nsresult GetSDCardStatus(nsAString& aPath, nsAString& aState);
 #endif
-
-#endif
--- a/dom/devicestorage/test/test_823965.html
+++ b/dom/devicestorage/test/test_823965.html
@@ -56,17 +56,29 @@ function getSuccess(e) {
 
 function getError(e) {
   ok(false, "getError was called : " + e.target.error.name);
   devicestorage_cleanup();
 }
 
 function addSuccess(e) {
 
-  ok(e.target.result == gFileName, "File name should match");
+  var filename = e.target.result;
+  if (filename[0] == "/") {
+    // We got /storgaeName/prefix/filename
+    // Remove the storageName (this shows up on FirefoxOS)
+    filename = filename.substring(1); // Remove leading slash
+    var slashIndex = filename.indexOf("/");
+    if (slashIndex >= 0) {
+      filename = filename.substring(slashIndex + 1); // Remove storageName
+    }
+  }
+  ok(filename == gFileName, "File name should match");
+  // Since we now have the fully qualified name, change gFileName to that.
+  gFileName = e.target.result;
 
   var storage = navigator.getDeviceStorage("pictures");
   request = storage.get(gFileName);
   request.onsuccess = getSuccess;
   request.onerror = getError;
 
   ok(true, "addSuccess was called");
 }
--- a/dom/devicestorage/test/test_enumerate.html
+++ b/dom/devicestorage/test/test_enumerate.html
@@ -29,24 +29,36 @@ function enumerateSuccess(e) {
   if (e.target.result == null) {
     ok(files.length == 0, "when the enumeration is done, we shouldn't have any files in this array")
     dump("We still have length = " + files.length + "\n");
     devicestorage_cleanup();
     return;
   }
   
   var filename = e.target.result.name;
+  if (filename[0] == "/") {
+    // We got /storgaeName/prefix/filename
+    // Remove the storageName (this shows up on FirefoxOS)
+    filename = filename.substring(1); // Remove leading slash
+    var slashIndex = filename.indexOf("/");
+    if (slashIndex >= 0) {
+      filename = filename.substring(slashIndex + 1); // Remove storageName
+    }
+  }
+  if (filename.startsWith(prefix)) {
+    filename = filename.substring(prefix.length + 1); // Remove prefix
+  }
 
   var index = files.indexOf(filename);
   files.remove(index);
 
   ok(index > -1, "filename should be in the enumeration : " + e.target.result.name);
 
   // clean up
-  var cleanup = storage.delete(prefix + "/" + filename);
+  var cleanup = storage.delete(e.target.result.name);
   cleanup.onsuccess = function(e) {}  // todo - can i remove this?
 
   e.target.continue();
 }
 
 function handleError(e) {
   ok(false, "handleError was called : " + e.target.error.name);
   devicestorage_cleanup();
--- a/dom/devicestorage/test/test_enumerateNoParam.html
+++ b/dom/devicestorage/test/test_enumerateNoParam.html
@@ -31,26 +31,40 @@ Array.prototype.remove = function(from, 
 
 devicestorage_setup();
 
 function enumerateSuccess(e) {
 
   if (e.target.result == null) {
     ok(files.length == 0, "when the enumeration is done, we shouldn't have any files in this array")
     devicestorage_cleanup();
+    return;
   }
   
   var filename = e.target.result.name;
-  var index = files.indexOf(filename);
+  if (filename[0] == "/") {
+    // We got /storgaeName/prefix/filename
+    // Remove the storageName (this shows up on FirefoxOS)
+    filename = filename.substring(1); // Remove leading slash
+    var slashIndex = filename.indexOf("/");
+    if (slashIndex >= 0) {
+      filename = filename.substring(slashIndex + 1); // Remove storageName
+    }
+  }
+  if (filename.startsWith(prefix)) {
+    filename = filename.substring(prefix.length + 1); // Remove prefix
+  }
+
+  var index = files.indexOf(enumFilename);
   files.remove(index);
 
   ok(index > -1, "filename should be in the enumeration : " + e.target.result.name);
 
   // clean up
-  var cleanup = storage.delete(prefix + "/" + filename);
+  var cleanup = storage.delete(e.target.result.name);
   cleanup.onsuccess = function(e) {}  // todo - can i remove this?
 
   e.target.continue();
 }
 
 function handleError(e) {
   ok(false, "handleError was called : " + e.target.error.name);
   devicestorage_cleanup();
--- a/dom/devicestorage/test/test_lastModificationFilter.html
+++ b/dom/devicestorage/test/test_lastModificationFilter.html
@@ -30,16 +30,28 @@ function verifyAndDelete(prefix, files, 
     ok(files.length == 0, "when the enumeration is done, we shouldn't have any files in this array")
     dump("We still have length = " + files.length + "\n");
     dump(files + "\n");
     devicestorage_cleanup();
     return;
   }
 
   var filename = e.target.result.name;
+  if (filename[0] == "/") {
+    // We got /storgaeName/prefix/filename
+    // Remove the storageName (this shows up on FirefoxOS)
+    filename = filename.substring(1); // Remove leading slash
+    var slashIndex = filename.indexOf("/");
+    if (slashIndex >= 0) {
+      filename = filename.substring(slashIndex + 1); // Remove storageName
+    }
+  }
+  if (filename.startsWith(prefix)) {
+    filename = filename.substring(prefix.length + 1); // Remove prefix
+  }
 
   var index = files.indexOf(filename);
   ok(index > -1, "filename should be in the enumeration : " + e.target.result.name);
   if (index == -1)
     return;
 
   files.remove(index);
 
--- a/dom/devicestorage/test/test_watch.html
+++ b/dom/devicestorage/test/test_watch.html
@@ -33,17 +33,27 @@ function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
   devicestorage_cleanup();
 }
 
 function onChange(e) {
 
   dump("we saw: " + e.path + " " + e.reason + "\n");
 
-  if (e.path == gFileName) {
+  var filename = e.path;
+  if (filename[0] == "/") {
+    // We got /storgaeName/prefix/filename
+    // Remove the storageName (this shows up on FirefoxOS)
+    filename = filename.substring(1); // Remove leading slash
+    var slashIndex = filename.indexOf("/");
+    if (slashIndex >= 0) {
+      filename = filename.substring(slashIndex + 1); // Remove storageName
+    }
+  }
+  if (filename == gFileName) {
     ok(true, "we saw the file get created");
     storage.removeEventListener("change", onChange);
     devicestorage_cleanup();
   }
   else {
     // we may see other file changes during the test, and
     // that is completely ok
   }
--- a/dom/devicestorage/test/test_watchOther.html
+++ b/dom/devicestorage/test/test_watchOther.html
@@ -33,17 +33,27 @@ function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
   devicestorage_cleanup();
 }
 
 function onChange(e) {
 
   dump("we saw: " + e.path + " " + e.reason + "\n");
 
-  if (e.path == gFileName) {
+  var filename = e.path;
+  if (filename[0] == "/") {
+    // We got /storgaeName/prefix/filename
+    // Remove the storageName (this shows up on FirefoxOS)
+    filename = filename.substring(1); // Remove leading slash
+    var slashIndex = filename.indexOf("/");
+    if (slashIndex >= 0) {
+      filename = filename.substring(slashIndex + 1); // Remove storageName
+    }
+  }
+  if (filename == gFileName) {
     ok(true, "we saw the file get created");
     storage.removeEventListener("change", onChange);
     devicestorage_cleanup();
   }
   else {
     // we may see other file changes during the test, and
     // that is completely ok
   }
--- a/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl
+++ b/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl
@@ -11,17 +11,17 @@ interface nsIDOMDeviceStorageChangeEvent
 interface nsIDOMEventListener;
 interface nsIFile;
 
 dictionary DeviceStorageEnumerationParameters
 {
   jsval since;
 };
 
-[scriptable, uuid(b6274c63-daa2-4c7a-aa45-b72e45b5f2d2), builtinclass]
+[scriptable, uuid(3d336180-b130-4b54-b205-ce74e36e733f), builtinclass]
 interface nsIDOMDeviceStorage : nsIDOMEventTarget
 {
     [implicit_jscontext] attribute jsval onchange;
     nsIDOMDOMRequest add(in nsIDOMBlob aBlob);
     nsIDOMDOMRequest addNamed(in nsIDOMBlob aBlob, in DOMString aName);
 
     [implicit_jscontext]
     nsIDOMDOMRequest get(in jsval aName);
@@ -39,14 +39,14 @@ interface nsIDOMDeviceStorage : nsIDOMEv
     nsIDOMDOMCursor enumerateEditable([optional] in jsval aName, /* DeviceStorageEnumerationParameters */ [optional] in jsval options);
 
     nsIDOMDOMRequest freeSpace();
 
     nsIDOMDOMRequest usedSpace();
 
     nsIDOMDOMRequest available();
 
-    // Note that the volumeName is just a name (like sdcard), and doesn't
+    // Note that the storageName is just a name (like sdcard), and doesn't
     // include any path information.
-    readonly attribute DOMString volumeName;
+    readonly attribute DOMString storageName;
 
     [noscript] readonly attribute nsIFile rootDirectory;
 };
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1157,44 +1157,47 @@ bool
 ContentChild::RecvLastPrivateDocShellDestroyed()
 {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->NotifyObservers(nullptr, "last-pb-context-exited", nullptr);
     return true;
 }
 
 bool
-ContentChild::RecvFilePathUpdate(const nsString& type, const nsString& path, const nsCString& aReason)
+ContentChild::RecvFilePathUpdate(const nsString& aStorageType,
+                                 const nsString& aStorageName,
+                                 const nsString& aPath,
+                                 const nsCString& aReason)
 {
-    nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(type, path);
+    nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(aStorageType, aStorageName, aPath);
 
     nsString reason;
     CopyASCIItoUTF16(aReason, reason);
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->NotifyObservers(dsf, "file-watcher-update", reason.get());
     return true;
 }
 
 bool
 ContentChild::RecvFileSystemUpdate(const nsString& aFsName,
-                                   const nsString& aName,
+                                   const nsString& aVolumeName,
                                    const int32_t& aState,
                                    const int32_t& aMountGeneration)
 {
 #ifdef MOZ_WIDGET_GONK
-    nsRefPtr<nsVolume> volume = new nsVolume(aFsName, aName, aState,
+    nsRefPtr<nsVolume> volume = new nsVolume(aFsName, aVolumeName, aState,
                                              aMountGeneration);
 
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     NS_ConvertUTF8toUTF16 stateStr(volume->StateStr());
     obs->NotifyObservers(volume, NS_VOLUME_STATE_CHANGED, stateStr.get());
 #else
     // Remove warnings about unused arguments
     unused << aFsName;
-    unused << aName;
+    unused << aVolumeName;
     unused << aState;
     unused << aMountGeneration;
 #endif
     return true;
 }
 
 bool
 ContentChild::RecvNotifyProcessPriorityChanged(
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -181,19 +181,22 @@ public:
 
     virtual bool RecvGarbageCollect();
     virtual bool RecvCycleCollect();
 
     virtual bool RecvAppInfo(const nsCString& version, const nsCString& buildID);
 
     virtual bool RecvLastPrivateDocShellDestroyed();
 
-    virtual bool RecvFilePathUpdate(const nsString& type, const nsString& path, const nsCString& reason);
+    virtual bool RecvFilePathUpdate(const nsString& aStorageType,
+                                    const nsString& aStorageName,
+                                    const nsString& aPath,
+                                    const nsCString& aReason);
     virtual bool RecvFileSystemUpdate(const nsString& aFsName,
-                                      const nsString& aName,
+                                      const nsString& aVolumeName,
                                       const int32_t& aState,
                                       const int32_t& aMountGeneration);
 
     virtual bool RecvNotifyProcessPriorityChanged(const hal::ProcessPriority& aPriority);
     virtual bool RecvMinimizeMemoryUsage();
     virtual bool RecvCancelMinimizeMemoryUsage();
 
 #ifdef ANDROID
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1505,17 +1505,17 @@ ContentParent::Observe(nsISupports* aSub
     else if (!strcmp(aTopic, "last-pb-context-exited")) {
         unused << SendLastPrivateDocShellDestroyed();
     }
     else if (!strcmp(aTopic, "file-watcher-update")) {
         nsCString creason;
         CopyUTF16toUTF8(aData, creason);
         DeviceStorageFile* file = static_cast<DeviceStorageFile*>(aSubject);
 
-        unused << SendFilePathUpdate(file->mStorageType, file->mPath, creason);
+        unused << SendFilePathUpdate(file->mStorageType, file->mStorageName, file->mPath, creason);
     }
 #ifdef MOZ_WIDGET_GONK
     else if(!strcmp(aTopic, NS_VOLUME_STATE_CHANGED)) {
         nsCOMPtr<nsIVolume> vol = do_QueryInterface(aSubject);
         if (!vol) {
             return NS_ERROR_NOT_AVAILABLE;
         }
 
@@ -2345,26 +2345,30 @@ ContentParent::RecvAsyncMessage(const ns
     ppm->ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(ppm.get()),
                         aMsg, false, &cloneData, nullptr, nullptr);
   }
   return true;
 }
 
 bool
 ContentParent::RecvFilePathUpdateNotify(const nsString& aType,
+                                        const nsString& aStorageName,
                                         const nsString& aFilePath,
                                         const nsCString& aReason)
 {
-    nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(aType, aFilePath);
+    nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(aType,
+                                                            aStorageName,
+                                                            aFilePath);
 
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (!obs) {
         return false;
     }
-    obs->NotifyObservers(dsf, "file-watcher-update", NS_ConvertASCIItoUTF16(aReason).get());
+    obs->NotifyObservers(dsf, "file-watcher-update",
+                         NS_ConvertASCIItoUTF16(aReason).get());
     return true;
 }
 
 static int32_t
 AddGeolocationListener(nsIDOMGeoPositionCallback* watcher, bool highAccuracy)
 {
   nsCOMPtr<nsIDOMGeoGeolocation> geo = do_GetService("@mozilla.org/geolocation;1");
   if (!geo) {
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -362,16 +362,17 @@ private:
 
     virtual bool RecvSyncMessage(const nsString& aMsg,
                                  const ClonedMessageData& aData,
                                  InfallibleTArray<nsString>* aRetvals);
     virtual bool RecvAsyncMessage(const nsString& aMsg,
                                   const ClonedMessageData& aData);
 
     virtual bool RecvFilePathUpdateNotify(const nsString& aType,
+                                          const nsString& aStorageName,
                                           const nsString& aFilePath,
                                           const nsCString& aReason);
 
     virtual bool RecvAddGeolocationListener(const IPC::Principal& aPrincipal,
                                             const bool& aHighAccuracy);
     virtual bool RecvRemoveGeolocationListener();
     virtual bool RecvSetGeolocationHigherAccuracy(const bool& aEnable);
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -60,55 +60,59 @@ struct FontListEntry {
     int16_t   stretch;
     uint8_t   italic;
     uint8_t   index;
 };
 
 struct DeviceStorageFreeSpaceParams
 {
   nsString type;
-  nsString relpath;
+  nsString storageName;
 };
 
 struct DeviceStorageUsedSpaceParams
 {
   nsString type;
-  nsString relpath;
+  nsString storageName;
 };
 
 struct DeviceStorageAvailableParams
 {
   nsString type;
-  nsString relpath;
+  nsString storageName;
 };
 
 struct DeviceStorageAddParams
 {
   nsString type;
+  nsString storageName;
   nsString relpath;
   PBlob blob;
 };
 
 struct DeviceStorageGetParams
 {
   nsString type;
+  nsString storageName;
   nsString rootDir;
   nsString relpath;
 };
 
 struct DeviceStorageDeleteParams
 {
   nsString type;
+  nsString storageName;
   nsString relpath;
 };
 
 struct DeviceStorageEnumerationParams
 {
   nsString type;
-  nsString relpath;
+  nsString storageName;
+  nsString rootdir;
   uint64_t since;
 };
 
 union DeviceStorageParams
 {
   DeviceStorageAddParams;
   DeviceStorageGetParams;
   DeviceStorageDeleteParams;
@@ -341,17 +345,18 @@ child:
      */
     ActivateA11y();
 
     AppInfo(nsCString version, nsCString buildID);
 
     // Notify child that last-pb-context-exited notification was observed
     LastPrivateDocShellDestroyed();
 
-    FilePathUpdate(nsString type, nsString filepath, nsCString reasons);
+    FilePathUpdate(nsString storageType, nsString storageName, nsString filepath,
+                   nsCString reasons);
 
     FileSystemUpdate(nsString fsName, nsString mountPoint, int32_t fsState,
                      int32_t mountGeneration);
 
     NotifyProcessPriorityChanged(ProcessPriority priority);
     MinimizeMemoryUsage();
     CancelMinimizeMemoryUsage();
 
@@ -489,16 +494,17 @@ parent:
 
     sync AudioChannelRegisterType(AudioChannelType aType);
     sync AudioChannelUnregisterType(AudioChannelType aType,
                                     bool aElementHidden);
 
     async AudioChannelChangedNotification();
 
     async FilePathUpdateNotify(nsString aType,
+                               nsString aStorageName,
                                nsString aFilepath,
                                nsCString aReason);
     // get nsIVolumeService to broadcast volume information
     async BroadcastVolume(nsString volumeName);
 
     async RecordingDeviceEvents(nsString recordingStatus);
 
     // Notify the parent that the child has finished handling a system message.
--- a/dom/system/gonk/nsVolumeService.cpp
+++ b/dom/system/gonk/nsVolumeService.cpp
@@ -191,19 +191,31 @@ NS_IMETHODIMP nsVolumeService::GetVolume
 }
 
 NS_IMETHODIMP
 nsVolumeService::GetVolumeByPath(const nsAString& aPath, nsIVolume **aResult)
 {
   nsCString utf8Path = NS_ConvertUTF16toUTF8(aPath);
   char realPathBuf[PATH_MAX];
 
-  if (!realpath(utf8Path.get(), realPathBuf)) {
-    ERR("GetVolumeByPath: realpath on '%s' failed: %d", utf8Path.get(), errno);
-    return NSRESULT_FOR_ERRNO();
+  while (realpath(utf8Path.get(), realPathBuf) < 0) {
+    if (errno != ENOENT) {
+      ERR("GetVolumeByPath: realpath on '%s' failed: %d", utf8Path.get(), errno);
+      return NSRESULT_FOR_ERRNO();
+    }
+    // The pathname we were passed doesn't exist, so we try stripping off trailing
+    // components until we get a successful call to realpath, or until we run out
+    // of components (if we finally get to /something then we also stop).
+    int32_t slashIndex = utf8Path.RFindChar('/');
+    if ((slashIndex == kNotFound) || (slashIndex == 0)) {
+      errno = ENOENT;
+      ERR("GetVolumeByPath: realpath on '%s' failed.", utf8Path.get());
+      return NSRESULT_FOR_ERRNO();
+    }
+    utf8Path = Substring(utf8Path, 0, slashIndex);
   }
 
   // The volume mount point is always a directory. Something like /mnt/sdcard
   // Once we have a full qualified pathname with symlinks removed (which is
   // what realpath does), we basically check if aPath starts with the mount
   // point, but we don't want to have /mnt/sdcard match /mnt/sdcardfoo but we
   // do want it to match /mnt/sdcard/foo
   // So we add a trailing slash to the mount point and the pathname passed in