Merge birch to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Sat, 11 May 2013 08:18:19 -0400
changeset 138311 013cc654fe527de195bca5400951a060ecb2fadd
parent 138293 179e29a23c5672c16d2a4fa4752ff85a75c8ab2e (current diff)
parent 138310 dbf0c9f4a4b9d74546ff2980d18d53ce1a4c75a8 (diff)
child 138312 af2811479de4cbf9ab4b5e136909b3f22f814ad4
push id3752
push userlsblakk@mozilla.com
push dateMon, 13 May 2013 17:21:10 +0000
treeherdermozilla-aurora@1580544aef0b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.0a1
Merge birch to m-c.
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -389,22 +389,23 @@ pref("dom.mozContacts.enabled", true);
 // WebAlarms
 pref("dom.mozAlarms.enabled", true);
 
 // SimplePush
 pref("services.push.enabled", true);
 // serverURL to be assigned by services team
 pref("services.push.serverURL", "");
 pref("services.push.userAgentID", "");
-// exponential back-off start is 5 seconds like in HTTP/1.1
+// Exponential back-off start is 5 seconds like in HTTP/1.1.
+// Maximum back-off is pingInterval.
 pref("services.push.retryBaseInterval", 5000);
-// WebSocket level ping transmit interval in seconds.
-pref("services.push.websocketPingInterval", 55);
-// exponential back-off end is 20 minutes
-pref("services.push.maxRetryInterval", 1200000);
+// Interval at which to ping PushServer to check connection status. In
+// milliseconds. If no reply is received within requestTimeout, the connection
+// is considered closed.
+pref("services.push.pingInterval", 1800000); // 30 minutes
 // How long before a DOMRequest errors as timeout
 pref("services.push.requestTimeout", 10000);
 // enable udp wakeup support
 pref("services.push.udp.wakeupEnabled", true);
 // port on which UDP server socket is bound
 pref("services.push.udp.port", 2442);
 
 // NetworkStats
--- 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/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/BluetoothHfpManager.cpp
@@ -457,25 +457,32 @@ BluetoothHfpManager::Get()
   BluetoothHfpManager* manager = new BluetoothHfpManager();
   NS_ENSURE_TRUE(manager->Init(), nullptr);
 
   gBluetoothHfpManager = manager;
   return gBluetoothHfpManager;
 }
 
 void
-BluetoothHfpManager::NotifySettings()
+BluetoothHfpManager::NotifyStatusChanged(const nsAString& aType)
 {
   nsString type, name;
   BluetoothValue v;
   InfallibleTArray<BluetoothNamedValue> parameters;
-  type.AssignLiteral("bluetooth-hfp-status-changed");
+  type = aType;
 
   name.AssignLiteral("connected");
-  v = IsConnected();
+  if (type.EqualsLiteral("bluetooth-hfp-status-changed")) {
+    v = IsConnected();
+  } else if (type.EqualsLiteral("bluetooth-sco-status-changed")) {
+    v = IsScoConnected();
+  } else {
+    NS_WARNING("Wrong type for NotifyStatusChanged");
+    return;
+  }
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("address");
   v = mDeviceAddress;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   if (!BroadcastSystemMessage(type, parameters)) {
     NS_WARNING("Failed to broadcast system message to settings");
@@ -1405,17 +1412,17 @@ BluetoothHfpManager::OnConnectSuccess(Bl
     mRunnable = nullptr;
   }
 
   mFirstCKPD = true;
 
   // Cache device path for NotifySettings() since we can't get socket address
   // when a headset disconnect with us
   mSocket->GetAddress(mDeviceAddress);
-  NotifySettings();
+  NotifyStatusChanged(NS_LITERAL_STRING("bluetooth-hfp-status-changed"));
 
   ListenSco();
 }
 
 void
 BluetoothHfpManager::OnConnectError(BluetoothSocket* aSocket)
 {
   // Failed to create a SCO socket
@@ -1456,17 +1463,17 @@ BluetoothHfpManager::OnDisconnect(Blueto
     // Do nothing when a listening server socket is closed.
     return;
   }
 
   mSocket = nullptr;
   DisconnectSco();
 
   Listen();
-  NotifySettings();
+  NotifyStatusChanged(NS_LITERAL_STRING("bluetooth-hfp-status-changed"));
   Reset();
 }
 
 void
 BluetoothHfpManager::OnGetServiceChannel(const nsAString& aDeviceAddress,
                                          const nsAString& aServiceUuid,
                                          int aChannel)
 {
@@ -1498,16 +1505,17 @@ BluetoothHfpManager::OnScoConnectSuccess
   // For active connection request, we need to reply the DOMRequest
   if (mScoRunnable) {
     DispatchBluetoothReply(mScoRunnable,
                            BluetoothValue(true), EmptyString());
     mScoRunnable = nullptr;
   }
 
   NotifyAudioManager(mDeviceAddress);
+  NotifyStatusChanged(NS_LITERAL_STRING("bluetooth-sco-status-changed"));
 
   mScoSocketStatus = mScoSocket->GetConnectionStatus();
 }
 
 void
 BluetoothHfpManager::OnScoConnectError()
 {
   if (mScoRunnable) {
@@ -1521,16 +1529,17 @@ BluetoothHfpManager::OnScoConnectError()
 }
 
 void
 BluetoothHfpManager::OnScoDisconnect()
 {
   if (mScoSocketStatus == SocketConnectionStatus::SOCKET_CONNECTED) {
     ListenSco();
     NotifyAudioManager(EmptyString());
+    NotifyStatusChanged(NS_LITERAL_STRING("bluetooth-sco-status-changed"));
   }
 }
 
 bool
 BluetoothHfpManager::IsConnected()
 {
   if (mSocket) {
     return mSocket->GetConnectionStatus() ==
@@ -1572,16 +1581,17 @@ BluetoothHfpManager::ConnectSco(Bluetoot
   mScoSocket->Disconnect();
 
   mScoRunnable = aRunnable;
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, false);
   nsresult rv = bs->GetScoSocket(mDeviceAddress, true, false, mScoSocket);
 
+  mScoSocketStatus = mSocket->GetConnectionStatus();
   return NS_SUCCEEDED(rv);
 }
 
 bool
 BluetoothHfpManager::DisconnectSco()
 {
   if (!mScoSocket) {
     NS_WARNING("BluetoothHfpManager is not connected");
--- a/dom/bluetooth/BluetoothHfpManager.h
+++ b/dom/bluetooth/BluetoothHfpManager.h
@@ -105,17 +105,17 @@ private:
   nsresult HandleVoiceConnectionChanged();
 
   bool Init();
   void Cleanup();
   void Reset();
   void ResetCallArray();
 
   void NotifyDialer(const nsAString& aCommand);
-  void NotifySettings();
+  void NotifyStatusChanged(const nsAString& aType);
   void NotifyAudioManager(const nsAString& aAddress);
 
   bool SendCommand(const char* aCommand, uint8_t aValue = 0);
   bool SendLine(const char* aMessage);
   void UpdateCIND(uint8_t aType, uint8_t aValue, bool aSend);
   void OnScoConnectSuccess();
   void OnScoConnectError();
   void OnScoDisconnect();
--- 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()
@@ -403,26 +440,19 @@ 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;
     }
-
-    nsString fullpath;
-    nsresult rv = file->mFile->GetPath(fullpath);
-    if (NS_FAILED(rv)) {
-      NS_WARNING("Could not get path from the nsIFile!");
-      return NS_OK;
-    }
-
     ContentChild::GetSingleton()->SendFilePathUpdateNotify(file->mStorageType,
-                                                           fullpath,
+                                                           file->mStorageName,
+                                                           file->mPath,
                                                            NS_ConvertUTF16toUTF8(aData));
   } else {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->NotifyObservers(aSubject, "file-watcher-update", aData);
   }
   return NS_OK;
 }
 
@@ -443,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()
@@ -550,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();
 }
 
@@ -694,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();
@@ -716,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;
   }
 
@@ -767,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);
@@ -802,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;
@@ -826,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);
@@ -879,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;
     }
 
@@ -938,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();
@@ -965,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;
@@ -1093,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");
@@ -1265,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();
@@ -1407,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);
@@ -1483,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:
@@ -1695,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;
@@ -1754,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;
@@ -1906,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;
         }
 
@@ -1932,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;
       }
 
@@ -2108,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);
@@ -2168,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);
@@ -2250,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);
 }
@@ -2280,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,
@@ -2341,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)
 {
@@ -2374,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;
 }
@@ -2422,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;
 }
@@ -2443,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;
 }
@@ -2463,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,
@@ -2543,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);
 
@@ -2590,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
@@ -2619,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) {
@@ -2662,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
@@ -2734,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,
@@ -2755,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_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/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -900,16 +900,22 @@ NoRequestDatabaseHelper::OnError()
 nsresult
 CreateObjectStoreHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
 
   PROFILER_LABEL("IndexedDB", "CreateObjectStoreHelper::DoDatabaseWork");
 
+  if (IndexedDatabaseManager::InLowDiskSpaceMode()) {
+    NS_WARNING("Refusing to create additional objectStore because disk space "
+               "is low!");
+    return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+  }
+
   nsCOMPtr<mozIStorageStatement> stmt =
     mTransaction->GetCachedStatement(NS_LITERAL_CSTRING(
     "INSERT INTO object_store (id, auto_increment, name, key_path) "
     "VALUES (:id, :auto_increment, :name, :key_path)"
   ));
   NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   mozStorageStatementScoper scoper(stmt);
@@ -980,16 +986,21 @@ DeleteObjectStoreHelper::DoDatabaseWork(
 nsresult
 CreateFileHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
 
   PROFILER_LABEL("IndexedDB", "CreateFileHelper::DoDatabaseWork");
 
+  if (IndexedDatabaseManager::InLowDiskSpaceMode()) {
+    NS_WARNING("Refusing to create file because disk space is low!");
+    return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+  }
+
   FileManager* fileManager = mDatabase->Manager();
 
   mFileInfo = fileManager->GetNewFileInfo();
   NS_ENSURE_TRUE(mFileInfo, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   const int64_t& fileId = mFileInfo->Id();
 
   nsCOMPtr<nsIFile> directory = fileManager->EnsureJournalDirectory();
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -2940,16 +2940,21 @@ nsresult
 AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
   NS_ASSERTION(aConnection, "Passed a null connection!");
 
   PROFILER_LABEL("IndexedDB", "AddHelper::DoDatabaseWork");
 
+  if (IndexedDatabaseManager::InLowDiskSpaceMode()) {
+    NS_WARNING("Refusing to add more data because disk space is low!");
+    return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+  }
+
   nsresult rv;
   bool keyUnset = mKey.IsUnset();
   int64_t osid = mObjectStore->Id();
   const KeyPath& keyPath = mObjectStore->GetKeyPath();
 
   // The "|| keyUnset" here is mostly a debugging tool. If a key isn't
   // specified we should never have a collision and so it shouldn't matter
   // if we allow overwrite or not. By not allowing overwrite we raise
@@ -3909,16 +3914,21 @@ OpenCursorHelper::UnpackResponseFromPare
 nsresult
 CreateIndexHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
 
   PROFILER_LABEL("IndexedDB", "CreateIndexHelper::DoDatabaseWork");
 
+  if (IndexedDatabaseManager::InLowDiskSpaceMode()) {
+    NS_WARNING("Refusing to create index because disk space is low!");
+    return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+  }
+
   // Insert the data into the database.
   nsCOMPtr<mozIStorageStatement> stmt =
     mTransaction->GetCachedStatement(
     "INSERT INTO object_store_index (id, name, key_path, unique_index, "
       "multientry, object_store_id) "
     "VALUES (:id, :name, :key_path, :unique, :multientry, :osid)"
   );
   NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -2,35 +2,43 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "IndexedDatabaseManager.h"
 
 #include "nsIConsoleService.h"
+#include "nsIDiskSpaceWatcher.h"
 #include "nsIDOMScriptObjectFactory.h"
 #include "nsIFile.h"
 #include "nsIFileStorage.h"
+#include "nsIObserverService.h"
 #include "nsIScriptError.h"
 
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/Utilities.h"
 #include "mozilla/dom/TabContext.h"
+#include "mozilla/Services.h"
 #include "mozilla/storage.h"
 #include "nsContentUtils.h"
 #include "nsEventDispatcher.h"
 #include "nsThreadUtils.h"
 
 #include "IDBEvents.h"
 #include "IDBFactory.h"
 #include "IDBKeyRange.h"
 #include "IDBRequest.h"
 
+// The two possible values for the data argument when receiving the disk space
+// observer notification.
+#define LOW_DISK_SPACE_DATA_FULL "full"
+#define LOW_DISK_SPACE_DATA_FREE "free"
+
 USING_INDEXEDDB_NAMESPACE
 using namespace mozilla::dom;
 USING_QUOTA_NAMESPACE
 
 static NS_DEFINE_CID(kDOMSOF_CID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID);
 
 namespace {
 
@@ -87,31 +95,50 @@ IndexedDatabaseManager::IndexedDatabaseM
 }
 
 IndexedDatabaseManager::~IndexedDatabaseManager()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 }
 
 bool IndexedDatabaseManager::sIsMainProcess = false;
+int32_t IndexedDatabaseManager::sLowDiskSpaceMode = 0;
 
 // static
 IndexedDatabaseManager*
 IndexedDatabaseManager::GetOrCreate()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (IsClosed()) {
     NS_ERROR("Calling GetOrCreate() after shutdown!");
     return nullptr;
   }
 
   if (!gInstance) {
     sIsMainProcess = XRE_GetProcessType() == GeckoProcessType_Default;
 
+    if (sIsMainProcess) {
+      // See if we're starting up in low disk space conditions.
+      nsCOMPtr<nsIDiskSpaceWatcher> watcher =
+        do_GetService(DISKSPACEWATCHER_CONTRACTID);
+      if (watcher) {
+        bool isDiskFull;
+        if (NS_SUCCEEDED(watcher->GetIsDiskFull(&isDiskFull))) {
+          sLowDiskSpaceMode = isDiskFull ? 1 : 0;
+        }
+        else {
+          NS_WARNING("GetIsDiskFull failed!");
+        }
+      }
+      else {
+        NS_WARNING("No disk space watcher component available!");
+      }
+    }
+
     nsRefPtr<IndexedDatabaseManager> instance(new IndexedDatabaseManager());
 
     nsresult rv = instance->Init();
     NS_ENSURE_SUCCESS(rv, nullptr);
 
     if (PR_ATOMIC_SET(&gInitialized, 1)) {
       NS_ERROR("Initialized more than once?!");
     }
@@ -141,23 +168,37 @@ IndexedDatabaseManager::FactoryCreate()
   IndexedDatabaseManager* mgr = GetOrCreate();
   NS_IF_ADDREF(mgr);
   return mgr;
 }
 
 nsresult
 IndexedDatabaseManager::Init()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
   // Make sure that the quota manager is up.
-  NS_ENSURE_TRUE(QuotaManager::GetOrCreate(), NS_ERROR_FAILURE);
+  QuotaManager* qm = QuotaManager::GetOrCreate();
+  NS_ENSURE_STATE(qm);
 
-  // Must initialize the storage service on the main thread.
-  nsCOMPtr<mozIStorageService> ss =
-    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
-  NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE);
+  // During Init() we can't yet call IsMainProcess(), just check sIsMainProcess
+  // directly.
+  if (sIsMainProcess) {
+    // Must initialize the storage service on the main thread.
+    nsCOMPtr<mozIStorageService> ss =
+      do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+    NS_ENSURE_STATE(ss);
+
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    NS_ENSURE_STATE(obs);
+
+    nsresult rv =
+      obs->AddObserver(this, DISKSPACEWATCHER_OBSERVER_TOPIC, false);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   return NS_OK;
 }
 
 void
 IndexedDatabaseManager::Destroy()
 {
   // Setting the closed flag prevents the service from being recreated.
@@ -274,26 +315,36 @@ IndexedDatabaseManager::TabContextMayAcc
 // static
 bool
 IndexedDatabaseManager::IsClosed()
 {
   return !!gClosed;
 }
 
 #ifdef DEBUG
-//static
+// static
 bool
 IndexedDatabaseManager::IsMainProcess()
 {
   NS_ASSERTION(gInstance,
                "IsMainProcess() called before indexedDB has been initialized!");
   NS_ASSERTION((XRE_GetProcessType() == GeckoProcessType_Default) ==
                sIsMainProcess, "XRE_GetProcessType changed its tune!");
   return sIsMainProcess;
 }
+
+//static
+bool
+IndexedDatabaseManager::InLowDiskSpaceMode()
+{
+  NS_ASSERTION(gInstance,
+               "InLowDiskSpaceMode() called before indexedDB has been "
+               "initialized!");
+  return !!sLowDiskSpaceMode;
+}
 #endif
 
 already_AddRefed<FileManager>
 IndexedDatabaseManager::GetFileManager(const nsACString& aOrigin,
                                        const nsAString& aDatabaseName)
 {
   nsTArray<nsRefPtr<FileManager> >* array;
   if (!mFileManagers.Get(aOrigin, &array)) {
@@ -389,17 +440,18 @@ IndexedDatabaseManager::AsyncDeleteFile(
     quotaManager->IOThread()->Dispatch(runnable, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMPL_ADDREF(IndexedDatabaseManager)
 NS_IMPL_RELEASE_WITH_DESTROY(IndexedDatabaseManager, Destroy())
-NS_IMPL_QUERY_INTERFACE1(IndexedDatabaseManager, nsIIndexedDatabaseManager)
+NS_IMPL_QUERY_INTERFACE2(IndexedDatabaseManager, nsIIndexedDatabaseManager,
+                                                 nsIObserver)
 
 NS_IMETHODIMP
 IndexedDatabaseManager::InitWindowless(const jsval& aObj, JSContext* aCx)
 {
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_ARG(!JSVAL_IS_PRIMITIVE(aObj));
 
   JS::Rooted<JSObject*> obj(aCx, JSVAL_TO_OBJECT(aObj));
@@ -449,16 +501,45 @@ IndexedDatabaseManager::InitWindowless(c
   if (!JS_DefineProperty(aCx, obj, "IDBKeyRange", OBJECT_TO_JSVAL(keyrangeObj),
                          nullptr, nullptr, JSPROP_ENUMERATE)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+IndexedDatabaseManager::Observe(nsISupports* aSubject, const char* aTopic,
+                                const PRUnichar* aData)
+{
+  NS_ASSERTION(IsMainProcess(), "Wrong process!");
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  if (!strcmp(aTopic, DISKSPACEWATCHER_OBSERVER_TOPIC)) {
+    NS_ASSERTION(aData, "No data?!");
+
+    const nsDependentString data(aData);
+
+    if (data.EqualsLiteral(LOW_DISK_SPACE_DATA_FULL)) {
+      PR_ATOMIC_SET(&sLowDiskSpaceMode, 1);
+    }
+    else if (data.EqualsLiteral(LOW_DISK_SPACE_DATA_FREE)) {
+      PR_ATOMIC_SET(&sLowDiskSpaceMode, 0);
+    }
+    else {
+      NS_NOTREACHED("Unknown data value!");
+    }
+
+    return NS_OK;
+  }
+
+   NS_NOTREACHED("Unknown topic!");
+   return NS_ERROR_UNEXPECTED;
+ }
+
 AsyncDeleteFileRunnable::AsyncDeleteFileRunnable(FileManager* aFileManager,
                                                  int64_t aFileId)
 : mFileManager(aFileManager), mFileId(aFileId)
 {
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(AsyncDeleteFileRunnable,
                               nsIRunnable)
--- a/dom/indexedDB/IndexedDatabaseManager.h
+++ b/dom/indexedDB/IndexedDatabaseManager.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_indexeddb_indexeddatabasemanager_h__
 #define mozilla_dom_indexeddb_indexeddatabasemanager_h__
 
 #include "mozilla/dom/indexedDB/IndexedDatabase.h"
 
 #include "nsIIndexedDatabaseManager.h"
+#include "nsIObserver.h"
 
 #include "mozilla/Mutex.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 
 #define INDEXEDDB_MANAGER_CONTRACTID "@mozilla.org/dom/indexeddb/manager;1"
 
 class nsIAtom;
@@ -26,21 +27,23 @@ namespace dom {
 class TabContext;
 }
 }
 
 BEGIN_INDEXEDDB_NAMESPACE
 
 class FileManager;
 
-class IndexedDatabaseManager MOZ_FINAL : public nsIIndexedDatabaseManager
+class IndexedDatabaseManager MOZ_FINAL : public nsIIndexedDatabaseManager,
+                                         public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIINDEXEDDATABASEMANAGER
+  NS_DECL_NSIOBSERVER
 
   // Returns a non-owning reference.
   static IndexedDatabaseManager*
   GetOrCreate();
 
   // Returns a non-owning reference.
   static IndexedDatabaseManager*
   Get();
@@ -57,16 +60,26 @@ public:
 #ifdef DEBUG
   ;
 #else
   {
     return sIsMainProcess;
   }
 #endif
 
+  static bool
+  InLowDiskSpaceMode()
+#ifdef DEBUG
+  ;
+#else
+  {
+    return !!sLowDiskSpaceMode;
+  }
+#endif
+
   already_AddRefed<FileManager>
   GetFileManager(const nsACString& aOrigin,
                  const nsAString& aDatabaseName);
 
   void
   AddFileManager(FileManager* aFileManager);
 
   void
@@ -116,13 +129,14 @@ private:
                    nsTArray<nsRefPtr<FileManager> > > mFileManagers;
 
   // Lock protecting FileManager.mFileInfos and nsDOMFileBase.mFileInfos
   // It's s also used to atomically update FileInfo.mRefCnt, FileInfo.mDBRefCnt
   // and FileInfo.mSliceRefCnt
   mozilla::Mutex mFileMutex;
 
   static bool sIsMainProcess;
+  static int32_t sLowDiskSpaceMode;
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif /* mozilla_dom_indexeddb_indexeddatabasemanager_h__ */
--- a/dom/indexedDB/OpenDatabaseHelper.cpp
+++ b/dom/indexedDB/OpenDatabaseHelper.cpp
@@ -1871,40 +1871,51 @@ OpenDatabaseHelper::CreateDatabaseConnec
                                         const nsACString& aOrigin,
                                         mozIStorageConnection** aConnection)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
 
   PROFILER_LABEL("IndexedDB", "OpenDatabaseHelper::CreateDatabaseConnection");
 
+  nsresult rv;
+  bool exists;
+
+  if (IndexedDatabaseManager::InLowDiskSpaceMode()) {
+    rv = aDBFile->Exists(&exists);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!exists) {
+      NS_WARNING("Refusing to create database because disk space is low!");
+      return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
+    }
+  }
+
   nsCOMPtr<nsIFileURL> dbFileUrl =
     IDBFactory::GetDatabaseFileURL(aDBFile, aOrigin);
   NS_ENSURE_TRUE(dbFileUrl, NS_ERROR_FAILURE);
 
   nsCOMPtr<mozIStorageService> ss =
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE);
 
   nsCOMPtr<mozIStorageConnection> connection;
-  nsresult rv =
-    ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection));
+  rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection));
   if (rv == NS_ERROR_FILE_CORRUPTED) {
     // If we're just opening the database during origin initialization, then
     // we don't want to erase any files. The failure here will fail origin
     // initialization too.
     if (aName.IsVoid()) {
       return rv;
     }
 
     // Nuke the database file.  The web services can recreate their data.
     rv = aDBFile->Remove(false);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    bool exists;
     rv = aFMDirectory->Exists(&exists);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (exists) {
       bool isDirectory;
       rv = aFMDirectory->IsDirectory(&isDirectory);
       NS_ENSURE_SUCCESS(rv, rv);
       NS_ENSURE_TRUE(isDirectory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -116,16 +116,17 @@ MOCHITEST_FILES = \
   test_app_isolation_oop.html \
   webapp_clearBrowserData.js \
   webapp_clearBrowserData_appFrame.html \
   webapp_clearBrowserData_browserFrame.html \
   $(NULL)
 
 #   test_bug847147.html disabled for timeouts
 #   test_writer_starvation.html  disabled for infinite loops, bug 595368
+#   test_lowDiskSpace.html disabled for perma-orange, bug 861903
 
 ifeq (browser,$(MOZ_BUILD_APP))
 MOCHITEST_BROWSER_FILES = \
   browser_forgetThisSite.js \
   browser_forgetThisSiteAdd.html \
   browser_forgetThisSiteGet.html \
   browserHelpers.js \
   browser_permissionsPrompt.html \
--- a/dom/indexedDB/test/helpers.js
+++ b/dom/indexedDB/test/helpers.js
@@ -132,16 +132,26 @@ function browserErrorHandler(event)
 }
 
 function unexpectedSuccessHandler()
 {
   ok(false, "Got success, but did not expect it!");
   finishTest();
 }
 
+function expectedErrorHandler(name)
+{
+  return function(event) {
+    is(event.type, "error", "Got an error event");
+    is(event.target.error.name, name, "Expected error was thrown.");
+    event.preventDefault();
+    grabEventAndContinueHandler(event);
+  };
+}
+
 function ExpectError(name, preventDefault)
 {
   this._name = name;
   this._preventDefault = preventDefault;
 }
 ExpectError.prototype = {
   handleEvent: function(event)
   {
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_lowDiskSpace.html
@@ -0,0 +1,19 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Low Disk Space Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7" src="unit/test_lowDiskSpace.js"></script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/dom/indexedDB/test/unit/Makefile.in
+++ b/dom/indexedDB/test/unit/Makefile.in
@@ -34,16 +34,17 @@ MOCHITEST_FILES = \
   test_index_object_cursors.js \
   test_index_update_delete.js \
   test_indexes.js \
   test_indexes_bad_values.js \
   test_indexes_funny_things.js \
   test_invalid_version.js \
   test_key_requirements.js \
   test_keys.js \
+  test_lowDiskSpace.js \
   test_multientry.js \
   test_names_sorted.js \
   test_object_identity.js \
   test_objectCursors.js \
   test_objectStore_inline_autoincrement_key_added_on_put.js \
   test_objectStore_remove_values.js \
   test_odd_result_order.js \
   test_open_empty_db.js \
--- a/dom/indexedDB/test/unit/head.js
+++ b/dom/indexedDB/test/unit/head.js
@@ -33,16 +33,20 @@ function isnot(a, b, msg) {
 function executeSoon(fun) {
   do_execute_soon(fun);
 }
 
 function todo(condition, name, diag) {
   dump("TODO: ", diag);
 }
 
+function info(msg) {
+  do_print(msg);
+}
+
 function run_test() {
   runTest();
 };
 
 function runTest()
 {
   // XPCShell does not get a profile by default.
   do_get_profile();
@@ -83,16 +87,26 @@ function errorHandler(event)
 }
 
 function unexpectedSuccessHandler()
 {
   do_check_true(false);
   finishTest();
 }
 
+function expectedErrorHandler(name)
+{
+  return function(event) {
+    do_check_eq(event.type, "error");
+    do_check_eq(event.target.error.name, name);
+    event.preventDefault();
+    grabEventAndContinueHandler(event);
+  };
+}
+
 function ExpectError(name)
 {
   this._name = name;
 }
 ExpectError.prototype = {
   handleEvent: function(event)
   {
     do_check_eq(event.type, "error");
@@ -177,10 +191,15 @@ function gc()
   Components.utils.forceCC();
 }
 
 var SpecialPowers = {
   isMainProcess: function() {
     return Components.classes["@mozilla.org/xre/app-info;1"]
                      .getService(Components.interfaces.nsIXULRuntime)
                      .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+  },
+  notifyObservers: function(subject, topic, data) {
+    var obsvc = Cc['@mozilla.org/observer-service;1']
+                   .getService(Ci.nsIObserverService);
+    obsvc.notifyObservers(subject, topic, data);
   }
 };
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/unit/test_lowDiskSpace.js
@@ -0,0 +1,736 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+var self = this;
+
+var testGenerator = testSteps();
+
+function testSteps()
+{
+  const dbName = self.window ? window.location.pathname : "test_lowDiskSpace";
+  const dbVersion = 1;
+
+  const objectStoreName = "foo";
+  const objectStoreOptions = { keyPath: "foo" };
+
+  const indexName = "bar";
+  const indexOptions = { unique: true };
+
+  const dbData = [
+    { foo: 0, bar: 0 },
+    { foo: 1, bar: 10 },
+    { foo: 2, bar: 20 },
+    { foo: 3, bar: 30 },
+    { foo: 4, bar: 40 },
+    { foo: 5, bar: 50 },
+    { foo: 6, bar: 60 },
+    { foo: 7, bar: 70 },
+    { foo: 8, bar: 80 },
+    { foo: 9, bar: 90 }
+  ];
+
+  let lowDiskMode = false;
+  function setLowDiskMode(val) {
+    let data = val ? "full" : "free";
+
+    if (val == lowDiskMode) {
+      info("Low disk mode is: " + data);
+    }
+    else {
+      info("Changing low disk mode to: " + data);
+      SpecialPowers.notifyObservers(null, "disk-space-watcher", data);
+      lowDiskMode = val;
+    }
+  }
+
+  { // Make sure opening works from the beginning.
+    info("Test 1");
+
+    setLowDiskMode(false);
+
+    let request = indexedDB.open(dbName, dbVersion);
+    request.onerror = errorHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    let event = yield;
+
+    is(event.type, "success", "Opened database without setting low disk mode");
+
+    let db = event.target.result;
+    db.close();
+  }
+
+  { // Make sure delete works in low disk mode.
+    info("Test 2");
+
+    setLowDiskMode(true);
+
+    let request = indexedDB.deleteDatabase(dbName);
+    request.onerror = errorHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    let event = yield;
+
+    is(event.type, "success", "Deleted database after setting low disk mode");
+  }
+
+  { // Make sure creating a db in low disk mode fails.
+    info("Test 3");
+
+    setLowDiskMode(true);
+
+    let request = indexedDB.open(dbName, dbVersion);
+    request.onerror = expectedErrorHandler("QuotaExceededError");
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+    let event = yield;
+
+    is(event.type, "error", "Didn't create new database in low disk mode");
+  }
+
+  { // Make sure opening an already-existing db in low disk mode succeeds.
+    info("Test 4");
+
+    setLowDiskMode(false);
+
+    let request = indexedDB.open(dbName, dbVersion);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+    let event = yield;
+
+    is(event.type, "upgradeneeded", "Upgrading database");
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.type, "success", "Created database");
+    ok(event.target.result === db, "Got the same database");
+
+    db.close();
+
+    setLowDiskMode(true);
+
+    request = indexedDB.open(dbName);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.type, "success", "Opened existing database in low disk mode");
+
+    db = event.target.result;
+    db.close();
+  }
+
+  { // Make sure upgrading an already-existing db in low disk mode succeeds.
+    info("Test 5");
+
+    setLowDiskMode(true);
+
+    let request = indexedDB.open(dbName, dbVersion + 1);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+
+    let event = yield;
+
+    is(event.type, "upgradeneeded", "Upgrading database");
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.type, "success", "Created database");
+    ok(event.target.result === db, "Got the same database");
+
+    db.close();
+  }
+
+  { // Make sure creating objectStores in low disk mode fails.
+    info("Test 6");
+
+    setLowDiskMode(true);
+
+    let request = indexedDB.open(dbName, dbVersion + 2);
+    request.onerror = expectedErrorHandler("QuotaExceededError");
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+
+    let event = yield;
+
+    is(event.type, "upgradeneeded", "Upgrading database");
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    let objectStore = db.createObjectStore(objectStoreName, objectStoreOptions);
+
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    event = yield;
+
+    is(event.type, "error", "Failed database upgrade");
+  }
+
+  { // Make sure creating indexes in low disk mode fails.
+    info("Test 7");
+
+    setLowDiskMode(false);
+
+    let request = indexedDB.open(dbName, dbVersion + 2);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+
+    let event = yield;
+
+    is(event.type, "upgradeneeded", "Upgrading database");
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    let objectStore = db.createObjectStore(objectStoreName, objectStoreOptions);
+
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.type, "success", "Upgraded database");
+    ok(event.target.result === db, "Got the same database");
+
+    db.close();
+
+    setLowDiskMode(true);
+
+    request = indexedDB.open(dbName, dbVersion + 3);
+    request.onerror = expectedErrorHandler("QuotaExceededError");
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+    event = yield;
+
+    is(event.type, "upgradeneeded", "Upgrading database");
+
+    db = event.target.result;
+    db.onerror = errorHandler;
+
+    objectStore = event.target.transaction.objectStore(objectStoreName);
+    let index = objectStore.createIndex(indexName, indexName, indexOptions);
+
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    event = yield;
+
+    is(event.type, "error", "Failed database upgrade");
+  }
+
+  { // Make sure deleting indexes in low disk mode succeeds.
+    info("Test 8");
+
+    setLowDiskMode(false);
+
+    let request = indexedDB.open(dbName, dbVersion + 3);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+
+    let event = yield;
+
+    is(event.type, "upgradeneeded", "Upgrading database");
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    let objectStore = event.target.transaction.objectStore(objectStoreName);
+    let index = objectStore.createIndex(indexName, indexName, indexOptions);
+
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.type, "success", "Upgraded database");
+    ok(event.target.result === db, "Got the same database");
+
+    db.close();
+
+    setLowDiskMode(true);
+
+    request = indexedDB.open(dbName, dbVersion + 4);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+    event = yield;
+
+    is(event.type, "upgradeneeded", "Upgrading database");
+
+    db = event.target.result;
+    db.onerror = errorHandler;
+
+    objectStore = event.target.transaction.objectStore(objectStoreName);
+    objectStore.deleteIndex(indexName);
+
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.type, "success", "Upgraded database");
+    ok(event.target.result === db, "Got the same database");
+
+    db.close();
+  }
+
+  { // Make sure deleting objectStores in low disk mode succeeds.
+    info("Test 9");
+
+    setLowDiskMode(true);
+
+    let request = indexedDB.open(dbName, dbVersion + 5);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+
+    let event = yield;
+
+    is(event.type, "upgradeneeded", "Upgrading database");
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    db.deleteObjectStore(objectStoreName);
+
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.type, "success", "Upgraded database");
+    ok(event.target.result === db, "Got the same database");
+
+    db.close();
+
+    // Reset everything.
+    indexedDB.deleteDatabase(dbName);
+  }
+
+
+  { // Add data that the rest of the tests will use.
+    info("Adding test data");
+
+    setLowDiskMode(false);
+
+    let request = indexedDB.open(dbName, dbVersion);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = unexpectedSuccessHandler;
+    let event = yield;
+
+    is(event.type, "upgradeneeded", "Upgrading database");
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    let objectStore = db.createObjectStore(objectStoreName, objectStoreOptions);
+    let index = objectStore.createIndex(indexName, indexName, indexOptions);
+
+    for each (let data in dbData) {
+      objectStore.add(data);
+    }
+
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.type, "success", "Upgraded database");
+    ok(event.target.result === db, "Got the same database");
+
+    db.close();
+  }
+
+  { // Make sure read operations in readonly transactions succeed in low disk
+    // mode.
+    info("Test 10");
+
+    setLowDiskMode(true);
+
+    let request = indexedDB.open(dbName, dbVersion);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    let event = yield;
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    let transaction = db.transaction(objectStoreName);
+    let objectStore = transaction.objectStore(objectStoreName);
+    let index = objectStore.index(indexName);
+
+    let data = dbData[0];
+
+    let requestCounter = new RequestCounter();
+
+    objectStore.get(data.foo).onsuccess = requestCounter.handler();
+    objectStore.mozGetAll().onsuccess = requestCounter.handler();
+    objectStore.count().onsuccess = requestCounter.handler();
+    index.get(data.bar).onsuccess = requestCounter.handler();
+    index.mozGetAll().onsuccess = requestCounter.handler();
+    index.getKey(data.bar).onsuccess = requestCounter.handler();
+    index.mozGetAllKeys().onsuccess = requestCounter.handler();
+    index.count().onsuccess = requestCounter.handler();
+
+    let objectStoreDataCount = 0;
+
+    request = objectStore.openCursor();
+    request.onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (cursor) {
+        objectStoreDataCount++;
+        objectStoreDataCount % 2 ? cursor.continue() : cursor.advance(1);
+      }
+      else {
+        is(objectStoreDataCount, dbData.length, "Saw all data");
+        requestCounter.decr();
+      }
+    };
+    requestCounter.incr();
+
+    let indexDataCount = 0;
+
+    request = index.openCursor();
+    request.onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (cursor) {
+        indexDataCount++;
+        indexDataCount % 2 ? cursor.continue() : cursor.advance(1);
+      }
+      else {
+        is(indexDataCount, dbData.length, "Saw all data");
+        requestCounter.decr();
+      }
+    };
+    requestCounter.incr();
+
+    let indexKeyDataCount = 0;
+
+    request = index.openCursor();
+    request.onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (cursor) {
+        indexKeyDataCount++;
+        indexKeyDataCount % 2 ? cursor.continue() : cursor.advance(1);
+      }
+      else {
+        is(indexKeyDataCount, dbData.length, "Saw all data");
+        requestCounter.decr();
+      }
+    };
+    requestCounter.incr();
+
+    // Wait for all requests.
+    yield;
+
+    transaction.oncomplete = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.type, "complete", "Transaction succeeded");
+
+    db.close();
+  }
+
+  { // Make sure read operations in readwrite transactions succeed in low disk
+    // mode.
+    info("Test 11");
+
+    setLowDiskMode(true);
+
+    let request = indexedDB.open(dbName, dbVersion);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    let event = yield;
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    let transaction = db.transaction(objectStoreName, "readwrite");
+    let objectStore = transaction.objectStore(objectStoreName);
+    let index = objectStore.index(indexName);
+
+    let data = dbData[0];
+
+    let requestCounter = new RequestCounter();
+
+    objectStore.get(data.foo).onsuccess = requestCounter.handler();
+    objectStore.mozGetAll().onsuccess = requestCounter.handler();
+    objectStore.count().onsuccess = requestCounter.handler();
+    index.get(data.bar).onsuccess = requestCounter.handler();
+    index.mozGetAll().onsuccess = requestCounter.handler();
+    index.getKey(data.bar).onsuccess = requestCounter.handler();
+    index.mozGetAllKeys().onsuccess = requestCounter.handler();
+    index.count().onsuccess = requestCounter.handler();
+
+    let objectStoreDataCount = 0;
+
+    request = objectStore.openCursor();
+    request.onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (cursor) {
+        objectStoreDataCount++;
+        objectStoreDataCount % 2 ? cursor.continue() : cursor.advance(1);
+      }
+      else {
+        is(objectStoreDataCount, dbData.length, "Saw all data");
+        requestCounter.decr();
+      }
+    };
+    requestCounter.incr();
+
+    let indexDataCount = 0;
+
+    request = index.openCursor();
+    request.onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (cursor) {
+        indexDataCount++;
+        indexDataCount % 2 ? cursor.continue() : cursor.advance(1);
+      }
+      else {
+        is(indexDataCount, dbData.length, "Saw all data");
+        requestCounter.decr();
+      }
+    };
+    requestCounter.incr();
+
+    let indexKeyDataCount = 0;
+
+    request = index.openCursor();
+    request.onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (cursor) {
+        indexKeyDataCount++;
+        indexKeyDataCount % 2 ? cursor.continue() : cursor.advance(1);
+      }
+      else {
+        is(indexKeyDataCount, dbData.length, "Saw all data");
+        requestCounter.decr();
+      }
+    };
+    requestCounter.incr();
+
+    // Wait for all requests.
+    yield;
+
+    transaction.oncomplete = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.type, "complete", "Transaction succeeded");
+
+    db.close();
+  }
+
+  { // Make sure write operations in readwrite transactions fail in low disk
+    // mode.
+    info("Test 12");
+
+    setLowDiskMode(true);
+
+    let request = indexedDB.open(dbName, dbVersion);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    let event = yield;
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    let transaction = db.transaction(objectStoreName, "readwrite");
+    let objectStore = transaction.objectStore(objectStoreName);
+    let index = objectStore.index(indexName);
+
+    let data = dbData[0];
+    let newData = { foo: 999, bar: 999 };
+
+    let requestCounter = new RequestCounter();
+
+    objectStore.add(newData).onerror = requestCounter.errorHandler();
+    objectStore.put(newData).onerror = requestCounter.errorHandler();
+
+    objectStore.get(data.foo).onsuccess = requestCounter.handler();
+    objectStore.mozGetAll().onsuccess = requestCounter.handler();
+    objectStore.count().onsuccess = requestCounter.handler();
+    index.get(data.bar).onsuccess = requestCounter.handler();
+    index.mozGetAll().onsuccess = requestCounter.handler();
+    index.getKey(data.bar).onsuccess = requestCounter.handler();
+    index.mozGetAllKeys().onsuccess = requestCounter.handler();
+    index.count().onsuccess = requestCounter.handler();
+
+    let objectStoreDataCount = 0;
+
+    request = objectStore.openCursor();
+    request.onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (cursor) {
+        objectStoreDataCount++;
+        cursor.update(cursor.value).onerror = requestCounter.errorHandler();
+        objectStoreDataCount % 2 ? cursor.continue() : cursor.advance(1);
+      }
+      else {
+        is(objectStoreDataCount, dbData.length, "Saw all data");
+        requestCounter.decr();
+      }
+    };
+    requestCounter.incr();
+
+    let indexDataCount = 0;
+
+    request = index.openCursor();
+    request.onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (cursor) {
+        indexDataCount++;
+        cursor.update(cursor.value).onerror = requestCounter.errorHandler();
+        indexDataCount % 2 ? cursor.continue() : cursor.advance(1);
+      }
+      else {
+        is(indexDataCount, dbData.length, "Saw all data");
+        requestCounter.decr();
+      }
+    };
+    requestCounter.incr();
+
+    let indexKeyDataCount = 0;
+
+    request = index.openCursor();
+    request.onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (cursor) {
+        indexKeyDataCount++;
+        cursor.update(cursor.value).onerror = requestCounter.errorHandler();
+        indexKeyDataCount % 2 ? cursor.continue() : cursor.advance(1);
+      }
+      else {
+        is(indexKeyDataCount, dbData.length, "Saw all data");
+        requestCounter.decr();
+      }
+    };
+    requestCounter.incr();
+
+    // Wait for all requests.
+    yield;
+
+    transaction.oncomplete = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.type, "complete", "Transaction succeeded");
+
+    db.close();
+  }
+
+  { // Make sure deleting operations in readwrite transactions succeed in low
+    // disk mode.
+    info("Test 13");
+
+    setLowDiskMode(true);
+
+    let request = indexedDB.open(dbName, dbVersion);
+    request.onerror = errorHandler;
+    request.onupgradeneeded = unexpectedSuccessHandler;
+    request.onsuccess = grabEventAndContinueHandler;
+    let event = yield;
+
+    let db = event.target.result;
+    db.onerror = errorHandler;
+
+    let transaction = db.transaction(objectStoreName, "readwrite");
+    let objectStore = transaction.objectStore(objectStoreName);
+    let index = objectStore.index(indexName);
+
+    let dataIndex = 0;
+    let data = dbData[dataIndex++];
+
+    let requestCounter = new RequestCounter();
+
+    objectStore.delete(data.foo).onsuccess = requestCounter.handler();
+
+    objectStore.openCursor().onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (cursor) {
+        cursor.delete().onsuccess = requestCounter.handler();
+      }
+      requestCounter.decr();
+    };
+    requestCounter.incr();
+
+    index.openCursor(null, "prev").onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (cursor) {
+        cursor.delete().onsuccess = requestCounter.handler();
+      }
+      requestCounter.decr();
+    };
+    requestCounter.incr();
+
+    yield;
+
+    objectStore.count().onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.target.result, dbData.length - 3, "Actually deleted something");
+
+    objectStore.clear();
+    objectStore.count().onsuccess = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.target.result, 0, "Actually cleared");
+
+    transaction.oncomplete = grabEventAndContinueHandler;
+    event = yield;
+
+    is(event.type, "complete", "Transaction succeeded");
+
+    db.close();
+  }
+
+  finishTest();
+  yield;
+}
+
+function RequestCounter(expectedType) {
+  this._counter = 0;
+}
+RequestCounter.prototype = {
+  incr: function() {
+    this._counter++;
+  },
+
+  decr: function() {
+    if (!--this._counter) {
+      continueToNextStepSync();
+    }
+  },
+
+  handler: function(type, preventDefault) {
+    this.incr();
+    return function(event) {
+      is(event.type, type || "success", "Correct type");
+      this.decr();
+    }.bind(this);
+  },
+
+  errorHandler: function(eventType, errorName) {
+    this.incr();
+    return function(event) {
+      is(event.type, eventType || "error", "Correct type");
+      is(event.target.error.name, errorName || "QuotaExceededError",
+          "Correct error name");
+      event.preventDefault();
+      event.stopPropagation();
+      this.decr();
+    }.bind(this);
+  }
+};
--- a/dom/indexedDB/test/unit/xpcshell.ini
+++ b/dom/indexedDB/test/unit/xpcshell.ini
@@ -27,16 +27,19 @@ tail =
 [test_index_object_cursors.js]
 [test_index_update_delete.js]
 [test_indexes.js]
 [test_indexes_bad_values.js]
 [test_indexes_funny_things.js]
 [test_invalid_version.js]
 [test_key_requirements.js]
 [test_keys.js]
+[test_lowDiskSpace.js]
+# Bug 861903 - Test is perma-fail.
+skip-if = true
 [test_multientry.js]
 [test_names_sorted.js]
 [test_object_identity.js]
 [test_objectCursors.js]
 [test_objectStore_inline_autoincrement_key_added_on_put.js]
 [test_objectStore_remove_values.js]
 [test_odd_result_order.js]
 [test_open_empty_db.js]
--- 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/push/src/PushService.jsm
+++ b/dom/push/src/PushService.jsm
@@ -14,16 +14,17 @@ const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
+Cu.import("resource://gre/modules/AlarmService.jsm");
 
 this.EXPORTED_SYMBOLS = ["PushService"];
 
 const prefs = new Preferences("services.push.");
 
 const kPUSHDB_DB_NAME = "push";
 const kPUSHDB_DB_VERSION = 1; // Change this if the IndexedDB format changes
 const kPUSHDB_STORE_NAME = "push";
@@ -82,17 +83,18 @@ this.PushDB.prototype = {
     debug("put()");
 
     this.newTxn(
       "readwrite",
       kPUSHDB_STORE_NAME,
       function txnCb(aTxn, aStore) {
         debug("Going to put " + aChannelRecord.channelID);
         aStore.put(aChannelRecord).onsuccess = function setTxnResult(aEvent) {
-          debug("Request successful. Updated record ID: " + aEvent.target.result);
+          debug("Request successful. Updated record ID: " +
+                aEvent.target.result);
         };
       },
       aSuccessCb,
       aErrorCb
     );
   },
 
   /*
@@ -262,22 +264,22 @@ this.PushWebSocketListener.prototype = {
         return;
     this._pushService._wsOnServerClose(context, aStatusCode, aReason);
   }
 }
 
 // websocket states
 // websocket is off
 const STATE_SHUT_DOWN = 0;
-// websocket has been opened on client side, waiting for successful open
+// Websocket has been opened on client side, waiting for successful open.
 // (_wsOnStart)
 const STATE_WAITING_FOR_WS_START = 1;
-// websocket opened, hello sent, waiting for server reply (_handleHelloReply)
+// Websocket opened, hello sent, waiting for server reply (_handleHelloReply).
 const STATE_WAITING_FOR_HELLO = 2;
-// websocket operational, handshake completed, begin protocol messaging
+// Websocket operational, handshake completed, begin protocol messaging.
 const STATE_READY = 3;
 
 /**
  * The implementation of the SimplePush system. This runs in the B2G parent
  * process and is started on boot. It uses WebSockets to communicate with the
  * server and PushDB (IndexedDB) for persistence.
  */
 this.PushService = {
@@ -328,35 +330,33 @@ this.PushService = {
 
               delete this._pendingRequests[channelID];
               for (var i = this._requestQueue.length - 1; i >= 0; --i)
                 if (this._requestQueue[i].channelID == channelID)
                   this._requestQueue.splice(i, 1);
             }
           }
         }
-        else if (aSubject == this._retryTimeoutTimer) {
-          this._beginWSSetup();
-        }
         break;
       case "webapps-uninstall":
         debug("webapps-uninstall");
         let appsService = Cc["@mozilla.org/AppsService;1"]
                             .getService(Ci.nsIAppsService);
         var app = appsService.getAppFromObserverMessage(aData);
         if (!app) {
           debug("webapps-uninstall: No app found " + aData.origin);
           return;
         }
 
         this._db.getAllByManifestURL(app.manifestURL, function(records) {
           debug("Got " + records.length);
           for (var i = 0; i < records.length; i++) {
             this._db.delete(records[i].channelID, null, function() {
-              debug("app uninstall: " + app.manifestURL + " Could not delete entry " + records[i].channelID);
+              debug("app uninstall: " + app.manifestURL +
+                    " Could not delete entry " + records[i].channelID);
             });
             // courtesy, but don't establish a connection
             // just for it
             if (this._ws) {
               debug("Had a connection, so telling the server");
               this._request("unregister", {channelID: records[i].channelID});
             }
           }
@@ -384,35 +384,16 @@ this.PushService = {
 
   // keeps requests buffered if the websocket disconnects or is not connected
   _requestQueue: [],
   _ws: null,
   _pendingRequests: {},
   _currentState: STATE_SHUT_DOWN,
   _requestTimeout: 0,
   _requestTimeoutTimer: null,
-
-  /**
-   * How retries work:  The goal is to ensure websocket is always up on
-   * networks not supporting UDP. So the websocket should only be shutdown if
-   * onServerClose indicates UDP wakeup.  If WS is closed due to socket error,
-   * _socketError() is called.  The retry timer is started and when it times
-   * out, beginWSSetup() is called again.
-   *
-   * On a successful connection, the timer is cancelled if it is running and
-   * the values are reset to defaults.
-   *
-   * If we are in the middle of a timeout (i.e. waiting), but
-   * a register/unregister is called, we don't want to wait around anymore.
-   * _sendRequest will automatically call beginWSSetup(), which will cancel the
-   * timer. In addition since the state will have changed, even if a pending
-   * timer event comes in (because the timer fired the event before it was
-   * cancelled), so the connection won't be reset.
-   */
-  _retryTimeoutTimer: null,
   _retryFailCount: 0,
 
   /**
    * According to the WS spec, servers should immediately close the underlying
    * TCP connection after they close a WebSocket. This causes wsOnStop to be
    * called with error NS_BASE_STREAM_CLOSED. Since the client has to keep the
    * WebSocket up, it should try to reconnect. But if the server closes the
    * WebSocket because it will wake up the client via UDP, then the client
@@ -435,16 +416,18 @@ this.PushService = {
 
     let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
                  .getService(Ci.nsIMessageBroadcaster);
 
     kCHILD_PROCESS_MESSAGES.forEach(function addMessage(msgName) {
         ppmm.addMessageListener(msgName, this);
     }.bind(this));
 
+    this._alarmID = null;
+
     this._requestTimeout = prefs.get("requestTimeout");
 
     this._udpPort = prefs.get("udp.port");
 
     this._db.getAllChannelIDs(
       function(channelIDs) {
         if (channelIDs.length > 0) {
           debug("Found registered channelIDs. Starting WebSocket");
@@ -461,22 +444,26 @@ this.PushService = {
     // slightly different URLs.
     prefs.observe("serverURL", this);
   },
 
   _shutdownWS: function() {
     debug("shutdownWS()");
     this._currentState = STATE_SHUT_DOWN;
     this._willBeWokenUpByUDP = false;
+
     if (this._wsListener)
       this._wsListener._pushService = null;
     try {
         this._ws.close(0, null);
     } catch (e) {}
     this._ws = null;
+
+    this._waitingForPong = false;
+    this._stopAlarm();
   },
 
   _shutdown: function() {
     debug("_shutdown()");
 
     Services.obs.removeObserver(this, "network-interface-state-changed",
                                 false);
     Services.obs.removeObserver(this, "webapps-uninstall", false);
@@ -493,60 +480,66 @@ this.PushService = {
     // All pending requests (ideally none) are dropped at this point. We
     // shouldn't have any applications performing registration/unregistration
     // or receiving notifications.
     this._shutdownWS();
 
     // At this point, profile-change-net-teardown has already fired, so the
     // WebSocket has been closed with NS_ERROR_ABORT (if it was up) and will
     // try to reconnect. Stop the timer.
-    if (this._retryTimeoutTimer)
-      this._retryTimeoutTimer.cancel();
+    this._stopAlarm();
 
     if (this._requestTimeoutTimer)
       this._requestTimeoutTimer.cancel();
 
     debug("shutdown complete!");
   },
 
-  // aStatusCode is an NS error from Components.results
-  _socketError: function(aStatusCode) {
-    debug("socketError()");
+  /**
+   * How retries work:  The goal is to ensure websocket is always up on
+   * networks not supporting UDP. So the websocket should only be shutdown if
+   * onServerClose indicates UDP wakeup.  If WS is closed due to socket error,
+   * _reconnectAfterBackoff() is called.  The retry alarm is started and when
+   * it times out, beginWSSetup() is called again.
+   *
+   * On a successful connection, the alarm is cancelled in
+   * wsOnMessageAvailable() when the ping alarm is started.
+   *
+   * If we are in the middle of a timeout (i.e. waiting), but
+   * a register/unregister is called, we don't want to wait around anymore.
+   * _sendRequest will automatically call beginWSSetup(), which will cancel the
+   * timer. In addition since the state will have changed, even if a pending
+   * timer event comes in (because the timer fired the event before it was
+   * cancelled), so the connection won't be reset.
+   */
+  _reconnectAfterBackoff: function() {
+    debug("reconnectAfterBackoff()");
 
-    // Calculate new timeout, but cap it to
+    // Calculate new timeout, but cap it to pingInterval.
     var retryTimeout = prefs.get("retryBaseInterval") *
                        Math.pow(2, this._retryFailCount);
-
-    // It is easier to express the max interval as a pref in milliseconds,
-    // rather than have it as a number and make people do the calculation of
-    // retryBaseInterval * 2^maxRetryFailCount.
-    retryTimeout = Math.min(retryTimeout, prefs.get("maxRetryInterval"));
+    retryTimeout = Math.min(retryTimeout, prefs.get("pingInterval"));
 
     this._retryFailCount++;
 
     debug("Retry in " + retryTimeout + " Try number " + this._retryFailCount);
-
-    if (!this._retryTimeoutTimer) {
-      this._retryTimeoutTimer = Cc["@mozilla.org/timer;1"]
-                                  .createInstance(Ci.nsITimer);
-    }
-
-    this._retryTimeoutTimer.init(this,
-                                 retryTimeout,
-                                 Ci.nsITimer.TYPE_ONE_SHOT);
+    this._setAlarm(retryTimeout);
   },
 
   _beginWSSetup: function() {
     debug("beginWSSetup()");
     if (this._currentState != STATE_SHUT_DOWN) {
       debug("_beginWSSetup: Not in shutdown state! Current state " +
             this._currentState);
       return;
     }
 
+    // Stop any pending reconnects scheduled for the near future.
+    this._stopAlarm();
+
     var serverURL = prefs.get("serverURL");
     if (!serverURL) {
       debug("No services.push.serverURL found!");
       return;
     }
 
     var uri;
     try {
@@ -565,24 +558,111 @@ this.PushService = {
       debug("Push over an insecure connection (ws://) is not allowed!");
       return;
     }
     else {
       debug("Unsupported websocket scheme " + uri.scheme);
       return;
     }
 
+
     debug("serverURL: " + uri.spec);
     this._wsListener = new PushWebSocketListener(this);
     this._ws.protocol = "push-notification";
-    this._ws.pingInterval = prefs.get("websocketPingInterval");
     this._ws.asyncOpen(uri, serverURL, this._wsListener, null);
     this._currentState = STATE_WAITING_FOR_WS_START;
   },
 
+  /** |delay| should be in milliseconds. */
+  _setAlarm: function(delay) {
+    // Stop any existing alarm.
+    this._stopAlarm();
+
+    AlarmService.add(
+      {
+        date: new Date(Date.now() + delay),
+        ignoreTimezone: true
+      },
+      this._onAlarmFired.bind(this),
+      function onSuccess(alarmID) {
+        this._alarmID = alarmID;
+        debug("Set alarm " + delay + " in the future " + this._alarmID);
+      }.bind(this)
+    )
+  },
+
+  _stopAlarm: function() {
+    if (this._alarmID !== null) {
+      debug("Stopped existing alarm " + this._alarmID);
+      AlarmService.remove(this._alarmID);
+      this._alarmID = null;
+    }
+  },
+
+  /**
+   * There is only one alarm active at any time. This alarm has 3 intervals
+   * corresponding to 3 tasks.
+   *
+   * 1) Reconnect on ping timeout.
+   *    If we haven't received any messages from the server by the time this
+   *    alarm fires, the connection is closed and PushService tries to
+   *    reconnect, repurposing the alarm for (3).
+   *
+   * 2) Send a ping.
+   *    The protocol sends a ping ({}) on the wire every pingInterval ms. Once
+   *    it sends the ping, the alarm goes to task (1) which is waiting for
+   *    a pong. If data is received after the ping is sent,
+   *    _wsOnMessageAvailable() will reset the ping alarm (which cancels
+   *    waiting for the pong). So as long as the connection is fine, pong alarm
+   *    never fires.
+   *
+   * 3) Reconnect after backoff.
+   *    The alarm is set by _reconnectAfterBackoff() and increases in duration
+   *    every time we try and fail to connect.  When it triggers, websocket
+   *    setup begins again. On successful socket setup, the socket starts
+   *    receiving messages. The alarm now goes to (2) where it monitors the
+   *    WebSocket by sending a ping.  Since incoming data is a sign of the
+   *    connection being up, the ping alarm is reset every time data is
+   *    received.
+   */
+  _onAlarmFired: function() {
+    // Conditions are arranged in decreasing specificity.
+    // i.e. when _waitingForPong is true, other conditions are also true.
+    if (this._waitingForPong) {
+      debug("Did not receive pong in time. Reconnecting WebSocket.");
+      this._shutdownWS();
+      this._reconnectAfterBackoff();
+    }
+    else if (this._currentState == STATE_READY) {
+      // Send a ping.
+      // Bypass the queue; we don't want this to be kept pending.
+      this._ws.sendMsg('{}');
+      debug("Sent ping.");
+      this._waitingForPong = true;
+      this._setAlarm(prefs.get("requestTimeout"));
+    }
+    else if (this._alarmID !== null) {
+      debug("reconnect alarm fired.");
+      // Reconnect after back-off.
+      // The check for a non-null _alarmID prevents a situation where the alarm
+      // fires, but _shutdownWS() is called from another code-path (e.g.
+      // network state change) and we don't want to reconnect.
+      //
+      // It also handles the case where _beginWSSetup() is called from another
+      // code-path.
+      //
+      // alarmID will be non-null only when no shutdown/connect is
+      // called between _reconnectAfterBackoff() setting the alarm and the
+      // alarm firing.
+
+      // Websocket is shut down. Backoff interval expired, try to connect.
+      this._beginWSSetup();
+    }
+  },
+
   /**
    * Protocol handler invoked by server message.
    */
   _handleHelloReply: function(reply) {
     debug("handleHelloReply()");
     if (this._currentState != STATE_WAITING_FOR_HELLO) {
       debug("Unexpected state " + this._currentState +
             "(expected STATE_WAITING_FOR_HELLO)");
@@ -1123,19 +1203,16 @@ this.PushService = {
   _wsOnStart: function(context) {
     debug("wsOnStart()");
     if (this._currentState != STATE_WAITING_FOR_WS_START) {
       debug("NOT in STATE_WAITING_FOR_WS_START. Current state " +
             this._currentState + ". Skipping");
       return;
     }
 
-    if (this._retryTimeoutTimer)
-      this._retryTimeoutTimer.cancel();
-
     // Since we've had a successful connection reset the retry fail count.
     this._retryFailCount = 0;
 
     var data = {
       messageType: "hello",
     }
 
     if (this._UAID)
@@ -1172,27 +1249,35 @@ this.PushService = {
    * connection close status code.
    *
    * If we do not explicitly call ws.close() then statusCode is always
    * NS_BASE_STREAM_CLOSED, even on a successful close.
    */
   _wsOnStop: function(context, statusCode) {
     debug("wsOnStop()");
 
+    this._shutdownWS();
+
     if (statusCode != Cr.NS_OK &&
         !(statusCode == Cr.NS_BASE_STREAM_CLOSED && this._willBeWokenUpByUDP)) {
       debug("Socket error " + statusCode);
-      this._socketError(statusCode);
+      this._reconnectAfterBackoff();
     }
 
-    this._shutdownWS();
   },
 
   _wsOnMessageAvailable: function(context, message) {
     debug("wsOnMessageAvailable() " + message);
+
+    this._waitingForPong = false;
+
+    // Reset the ping timer.  Note: This path is executed at every step of the
+    // handshake, so this alarm does not need to be set explicitly at startup.
+    this._setAlarm(prefs.get("pingInterval"));
+
     var reply = undefined;
     try {
       reply = JSON.parse(message);
     } catch(e) {
       debug("Parsing JSON failed. text : " + message);
       return;
     }
 
@@ -1224,17 +1309,17 @@ this.PushService = {
     }
 
     this[handler](reply);
   },
 
   /**
    * The websocket should never be closed. Since we don't call ws.close(),
    * _wsOnStop() receives error code NS_BASE_STREAM_CLOSED (see comment in that
-   * function), which calls socketError and re-establishes the WebSocket
+   * function), which calls reconnect and re-establishes the WebSocket
    * connection.
    *
    * If the server said it'll use UDP for wakeup, we set _willBeWokenUpByUDP
    * and stop reconnecting in _wsOnStop().
    */
   _wsOnServerClose: function(context, aStatusCode, aReason) {
     debug("wsOnServerClose() " + aStatusCode + " " + aReason);
 
@@ -1288,19 +1373,19 @@ this.PushService = {
    * notifications.
    */
   onStopListening: function(aServ, aStatus) {
     debug("UDP Server socket was shutdown. Status: " + aStatus);
     this._beginWSSetup();
   },
 
   /**
-   * Get mobile network information to decide if the client is capable of being woken
-   * up by UDP (which currently just means having an mcc and mnc along with an
-   * IP).
+   * Get mobile network information to decide if the client is capable of being
+   * woken up by UDP (which currently just means having an mcc and mnc along
+   * with an IP).
    */
   _getNetworkState: function() {
     debug("getNetworkState()");
     try {
       var nm = Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager);
       if (nm.active && nm.active.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
         var mcp = Cc["@mozilla.org/ril/content-helper;1"].getService(Ci.nsIMobileConnectionProvider);
         if (mcp.iccInfo) {
--- 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
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -849,16 +849,21 @@ SpecialPowersAPI.prototype = {
                    .getService(Ci.nsIObserverService);
     obsvc.addObserver(obs, notification, weak);
   },
   removeObserver: function(obs, notification) {
     var obsvc = Cc['@mozilla.org/observer-service;1']
                    .getService(Ci.nsIObserverService);
     obsvc.removeObserver(obs, notification);
   },
+  notifyObservers: function(subject, topic, data) {
+    var obsvc = Cc['@mozilla.org/observer-service;1']
+                   .getService(Ci.nsIObserverService);
+    obsvc.notifyObservers(subject, topic, data);
+  },
 
   can_QI: function(obj) {
     return obj.QueryInterface !== undefined;
   },
   do_QueryInterface: function(obj, iface) {
     return obj.QueryInterface(Ci[iface]);
   },
 
--- a/toolkit/components/diskspacewatcher/nsIDiskSpaceWatcher.idl
+++ b/toolkit/components/diskspacewatcher/nsIDiskSpaceWatcher.idl
@@ -1,14 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
+%{ C++
+#ifdef XP_WIN
+#undef GetFreeSpace
+#endif
+%}
+
 [scriptable, uuid(3aceba74-2ed5-4e99-8fe4-06e90e2b8ef0)]
 interface nsIDiskSpaceWatcher : nsISupports
 {
   readonly attribute bool isDiskFull; // True if we are low on disk space.
   readonly attribute unsigned long long freeSpace; // The free space currently available.
 };
 
 %{ C++