Bug 858416 - Create a composite device storage interface. r=dougt
authorDave Hylands <dhylands@mozilla.com>
Sat, 11 May 2013 02:10:18 -0700
changeset 131788 ba7496b49c06b6a670a62a7c2d275eee52d38f1b
parent 131787 491005c97e8041ee268b3ca203d6e74b1496e4b4
child 131789 044d554846ff020bebc1503a5e00fdd6c21fd97e
push id27970
push userryanvm@gmail.com
push dateMon, 13 May 2013 16:02:11 +0000
treeherdermozilla-inbound@4f44cdf5aafc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdougt
bugs858416
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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