Merge birch to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Sat, 11 May 2013 08:18:19 -0400
changeset 142483 013cc654fe527de195bca5400951a060ecb2fadd
parent 142465 179e29a23c5672c16d2a4fa4752ff85a75c8ab2e (current diff)
parent 142482 dbf0c9f4a4b9d74546ff2980d18d53ce1a4c75a8 (diff)
child 142484 af2811479de4cbf9ab4b5e136909b3f22f814ad4
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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++