Bug 853732 - Improve Device Storage enumeration to avoid bad performance with some SD card. r=dylands r=dougt a=tef+
authorDoug Turner <doug.turner@gmail.com>
Thu, 02 May 2013 16:26:31 -0700
changeset 142448 b2770cf6dcbd8fddfb9774814743d29e1f6c030b
parent 142447 c10f86bf3439ceba778ac55c3369ab6b266c12ee
child 142449 4a695b132a4b302e2459e239c4dc8b9815a55b6e
push id350
push userbbajaj@mozilla.com
push dateMon, 29 Jul 2013 23:00:49 +0000
treeherdermozilla-release@064965b37dbd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdylands, dougt, tef
bugs853732
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 853732 - Improve Device Storage enumeration to avoid bad performance with some SD card. r=dylands r=dougt a=tef+
dom/devicestorage/DeviceStorage.h
dom/devicestorage/DeviceStorageRequestParent.cpp
dom/devicestorage/DeviceStorageRequestParent.h
dom/devicestorage/PDeviceStorageRequest.ipdl
dom/devicestorage/nsDeviceStorage.cpp
dom/devicestorage/nsDeviceStorage.h
--- a/dom/devicestorage/DeviceStorage.h
+++ b/dom/devicestorage/DeviceStorage.h
@@ -9,16 +9,22 @@
 
 #include "nsIDOMDeviceStorage.h"
 #include "nsIFile.h"
 #include "nsIPrincipal.h"
 #include "nsIObserver.h"
 #include "nsDOMEventTargetHelper.h"
 #include "mozilla/StaticPtr.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 mPath;
   nsString mStorageType;
   nsString mRootDir;
   bool mEditable;
@@ -43,17 +49,22 @@ public:
   bool IsSafePath(const nsAString& aPath);
 
   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);
 
-  static void DirectoryDiskUsage(nsIFile* aFile, uint64_t* aSoFar, const nsAString& aStorageType);
+  static void DirectoryDiskUsage(nsIFile* aFile,
+                                 uint64_t* aPicturesSoFar,
+                                 uint64_t* aVideosSoFar,
+                                 uint64_t* aMusicSoFar,
+                                 uint64_t* aTotalSoFar);
+
   static void GetRootDirectoryForType(const nsAString& aType,
                                       const nsAString& aVolName,
                                       nsIFile** aFile);
 private:
   void Init(const nsAString& aStorageType);
   void NormalizeFilePath();
   void AppendRelativePath(const nsAString& aPath);
 };
--- a/dom/devicestorage/DeviceStorageRequestParent.cpp
+++ b/dom/devicestorage/DeviceStorageRequestParent.cpp
@@ -19,16 +19,19 @@ namespace dom {
 namespace devicestorage {
 
 DeviceStorageRequestParent::DeviceStorageRequestParent(const DeviceStorageParams& aParams)
   : mParams(aParams)
   , mMutex("DeviceStorageRequestParent::mMutex")
   , mActorDestoryed(false)
 {
   MOZ_COUNT_CTOR(DeviceStorageRequestParent);
+
+  DebugOnly<DeviceStorageUsedSpaceCache*> usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet();
+  NS_ASSERTION(usedSpaceCache, "DeviceStorageUsedSpaceCache is null");
 }
 
 void
 DeviceStorageRequestParent::Dispatch()
 {
   switch (mParams.type()) {
     case DeviceStorageParams::TDeviceStorageAddParams:
     {
@@ -85,24 +88,25 @@ DeviceStorageRequestParent::Dispatch()
       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<UsedSpaceFileEvent> r = new UsedSpaceFileEvent(this, dsf);
 
-      nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
-      NS_ASSERTION(target, "Must have stream transport service");
-      target->Dispatch(r, NS_DISPATCH_NORMAL);
+      usedSpaceCache->Dispatch(r);
       break;
     }
 
     case DeviceStorageParams::TDeviceStorageAvailableParams:
     {
       DeviceStorageAvailableParams p = mParams;
 
       nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(p.type(), p.relpath());
@@ -249,17 +253,17 @@ DeviceStorageRequestParent::ActorDestroy
   mActorDestoryed = true;
   int32_t count = mRunnables.Length();
   for (int32_t index = 0; index < count; index++) {
     mRunnables[index]->Cancel();
   }
 }
 
 DeviceStorageRequestParent::PostFreeSpaceResultEvent::PostFreeSpaceResultEvent(DeviceStorageRequestParent* aParent,
-                                                                               int64_t aFreeSpace)
+                                                                               uint64_t aFreeSpace)
   : CancelableRunnable(aParent)
   , mFreeSpace(aFreeSpace)
 {
 }
 
 DeviceStorageRequestParent::PostFreeSpaceResultEvent::~PostFreeSpaceResultEvent() {}
 
 nsresult
@@ -267,18 +271,20 @@ DeviceStorageRequestParent::PostFreeSpac
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   FreeSpaceStorageResponse response(mFreeSpace);
   unused << mParent->Send__delete__(mParent, response);
   return NS_OK;
 }
 
 DeviceStorageRequestParent::PostUsedSpaceResultEvent::PostUsedSpaceResultEvent(DeviceStorageRequestParent* aParent,
-                                                                               int64_t aUsedSpace)
+                                                                               const nsAString& aType,
+                                                                               uint64_t aUsedSpace)
   : CancelableRunnable(aParent)
+  , mType(aType)
   , mUsedSpace(aUsedSpace)
 {
 }
 
 DeviceStorageRequestParent::PostUsedSpaceResultEvent::~PostUsedSpaceResultEvent() {}
 
 nsresult
 DeviceStorageRequestParent::PostUsedSpaceResultEvent::CancelableRun() {
@@ -483,17 +489,17 @@ DeviceStorageRequestParent::FreeSpaceFil
 
   nsCOMPtr<nsIRunnable> r;
   int64_t freeSpace = 0;
   nsresult rv = mFile->mFile->GetDiskSpaceAvailable(&freeSpace);
   if (NS_FAILED(rv)) {
     freeSpace = 0;
   }
 
-  r = new PostFreeSpaceResultEvent(mParent, freeSpace);
+  r = new PostFreeSpaceResultEvent(mParent, static_cast<uint64_t>(freeSpace));
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 DeviceStorageRequestParent::UsedSpaceFileEvent::UsedSpaceFileEvent(DeviceStorageRequestParent* aParent,
                                                                    DeviceStorageFile* aFile)
   : CancelableRunnable(aParent)
   , mFile(aFile)
@@ -504,21 +510,51 @@ DeviceStorageRequestParent::UsedSpaceFil
 {
 }
 
 nsresult
 DeviceStorageRequestParent::UsedSpaceFileEvent::CancelableRun()
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
+  DeviceStorageUsedSpaceCache* usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet();
+  NS_ASSERTION(usedSpaceCache, "DeviceStorageUsedSpaceCache is null");
+
   nsCOMPtr<nsIRunnable> r;
-  uint64_t diskUsage = 0;
-  DeviceStorageFile::DirectoryDiskUsage(mFile->mFile, &diskUsage, mFile->mStorageType);
+
+  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;
+  }
 
-  r = new PostUsedSpaceResultEvent(mParent, diskUsage);
+  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 {
+    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);
+    }
+  }
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 DeviceStorageRequestParent::ReadFileEvent::ReadFileEvent(DeviceStorageRequestParent* aParent,
                                                          DeviceStorageFile* aFile)
   : CancelableRunnable(aParent)
   , mFile(aFile)
@@ -562,17 +598,17 @@ DeviceStorageRequestParent::ReadFileEven
   PRTime modDate;
   rv = mFile->mFile->GetLastModifiedTime(&modDate);
   if (NS_FAILED(rv)) {
     r = new PostErrorEvent(mParent, POST_ERROR_EVENT_UNKNOWN);
     NS_DispatchToMainThread(r);
     return NS_OK;
   }
 
-  r = new PostBlobSuccessEvent(mParent, mFile, fileSize, mMimeType, modDate);
+  r = new PostBlobSuccessEvent(mParent, mFile, static_cast<uint64_t>(fileSize), mMimeType, modDate);
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 DeviceStorageRequestParent::EnumerateFileEvent::EnumerateFileEvent(DeviceStorageRequestParent* aParent,
                                                                    DeviceStorageFile* aFile,
                                                                    uint64_t aSince)
   : CancelableRunnable(aParent)
@@ -666,12 +702,11 @@ DeviceStorageRequestParent::PostAvailabl
   }
 #endif
 
   AvailableStorageResponse response(state);
   unused << mParent->Send__delete__(mParent, response);
   return NS_OK;
 }
 
-
 } // namespace devicestorage
 } // namespace dom
 } // namespace mozilla
--- a/dom/devicestorage/DeviceStorageRequestParent.h
+++ b/dom/devicestorage/DeviceStorageRequestParent.h
@@ -190,32 +190,34 @@ private:
       nsRefPtr<DeviceStorageFile> mFile;
       nsString mPath;
   };
 
  class PostFreeSpaceResultEvent : public CancelableRunnable
  {
     public:
       PostFreeSpaceResultEvent(DeviceStorageRequestParent* aParent,
-                               int64_t aFreeSpace);
+                               uint64_t aFreeSpace);
       virtual ~PostFreeSpaceResultEvent();
       virtual nsresult CancelableRun();
     private:
-      int64_t mFreeSpace;
+      uint64_t mFreeSpace;
  };
 
  class PostUsedSpaceResultEvent : public CancelableRunnable
  {
     public:
       PostUsedSpaceResultEvent(DeviceStorageRequestParent* aParent,
-                               int64_t aUsedSpace);
+                               const nsAString& aType,
+                               uint64_t aUsedSpace);
       virtual ~PostUsedSpaceResultEvent();
       virtual nsresult CancelableRun();
     private:
-      int64_t mUsedSpace;
+      nsString mType;
+      uint64_t mUsedSpace;
  };
 
  class PostAvailableResultEvent : public CancelableRunnable
  {
     public:
       PostAvailableResultEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
       virtual ~PostAvailableResultEvent();
       virtual nsresult CancelableRun();
--- a/dom/devicestorage/PDeviceStorageRequest.ipdl
+++ b/dom/devicestorage/PDeviceStorageRequest.ipdl
@@ -34,22 +34,22 @@ struct EnumerationResponse
 {
   nsString type;
   nsString relpath;
   DeviceStorageFileValue[] paths;
 };
 
 struct FreeSpaceStorageResponse
 {
-  int64_t freeBytes;
+  uint64_t freeBytes;
 };
 
 struct UsedSpaceStorageResponse
 {
-  int64_t usedBytes;
+  uint64_t usedBytes;
 };
 
 struct AvailableStorageResponse
 {
   nsString mountState;
 };
 
 union DeviceStorageResponseValue
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -12,16 +12,17 @@
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/devicestorage/PDeviceStorageRequestChild.h"
 #include "mozilla/dom/ipc/Blob.h"
 #include "mozilla/dom/PBrowserChild.h"
 #include "mozilla/dom/PContentPermissionRequestChild.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
+#include "mozilla/LazyIdleThread.h"
 
 #include "nsAutoPtr.h"
 #include "nsDOMEvent.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIFile.h"
 #include "nsIDirectoryEnumerator.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceDefs.h"
@@ -50,44 +51,140 @@
 #undef CreateEvent
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsIVolume.h"
 #include "nsIVolumeService.h"
 #endif
 
 #define DEVICESTORAGE_PROPERTIES "chrome://global/content/devicestorage.properties"
-#define DEVICESTORAGE_PICTURES   "pictures"
-#define DEVICESTORAGE_VIDEOS     "videos"
-#define DEVICESTORAGE_MUSIC      "music"
-#define DEVICESTORAGE_APPS       "apps"
-#define DEVICESTORAGE_SDCARD     "sdcard"
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
 
 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"));
+
+}
+
+DeviceStorageUsedSpaceCache::~DeviceStorageUsedSpaceCache()
+{
+}
+
+DeviceStorageUsedSpaceCache*
+DeviceStorageUsedSpaceCache::CreateOrGet()
+{
+  if (sDeviceStorageUsedSpaceCache) {
+    return sDeviceStorageUsedSpaceCache;
+  }
+
+  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;
+  }
+  *usedSize = mPicturesUsedSize;
+  return NS_OK;
+}
+
+nsresult
+DeviceStorageUsedSpaceCache::GetMusicUsedSize(uint64_t* usedSize) {
+  if (mDirty == true) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  *usedSize = mMusicUsedSize;
+  return NS_OK;
+}
+
+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) {
+    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);
+  }
+
+  if (aType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
+    return GetVideosUsedSize(usedSize);
+  }
+
+  if (aType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
+    return GetMusicUsedSize(usedSize);
+  }
+
+  if (aType.EqualsLiteral(DEVICESTORAGE_SDCARD)) {
+    return GetTotalUsedSize(usedSize);
+  }
+
+  return NS_ERROR_FAILURE;
+}
+
+void
+DeviceStorageUsedSpaceCache::SetUsedSizes(uint64_t aPictureSize,
+                                          uint64_t aVideosSize,
+                                          uint64_t aMusicSize,
+                                          uint64_t aTotalUsedSize) {
+    mPicturesUsedSize = aPictureSize;
+    mVideosUsedSize = aVideosSize;
+    mMusicUsedSize = aMusicSize;
+    mTotalUsedSize = aTotalUsedSize;
+    mDirty = false;
+}
+
 class GlobalDirs : public RefCounted<GlobalDirs>
 {
 public:
 #if !defined(MOZ_WIDGET_GONK)
   nsCOMPtr<nsIFile> pictures;
   nsCOMPtr<nsIFile> videos;
   nsCOMPtr<nsIFile> music;
   nsCOMPtr<nsIFile> apps;
   nsCOMPtr<nsIFile> sdcard;
 #endif
   nsCOMPtr<nsIFile> temp;
 };
 
 static StaticRefPtr<GlobalDirs> sDirs;
 
-nsAutoPtr<DeviceStorageTypeChecker> DeviceStorageTypeChecker::sDeviceStorageTypeChecker;
+StaticAutoPtr<DeviceStorageTypeChecker> DeviceStorageTypeChecker::sDeviceStorageTypeChecker;
 
 DeviceStorageTypeChecker::DeviceStorageTypeChecker()
 {
 }
 
 DeviceStorageTypeChecker::~DeviceStorageTypeChecker()
 {
 }
@@ -193,16 +290,47 @@ DeviceStorageTypeChecker::Check(const ns
 
   if (aType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
     return CaseInsensitiveFindInReadable(extensionMatch, mMusicExtensions);
   }
 
   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('.'));
+  if (dotIdx == kNotFound) {
+    return;
+  }
+
+  nsAutoString extensionMatch;
+  extensionMatch.AssignLiteral("*");
+  extensionMatch.Append(Substring(path, dotIdx));
+  extensionMatch.AppendLiteral(";");
+
+  if (CaseInsensitiveFindInReadable(extensionMatch, mPicturesExtensions)) {
+    aType.AssignLiteral(DEVICESTORAGE_PICTURES);
+  }
+  else if (CaseInsensitiveFindInReadable(extensionMatch, mVideosExtensions)) {
+    aType.AssignLiteral(DEVICESTORAGE_VIDEOS);
+  }
+  else if (CaseInsensitiveFindInReadable(extensionMatch, mMusicExtensions)) {
+    aType.AssignLiteral(DEVICESTORAGE_MUSIC);
+  }
+}
+
 nsresult
 DeviceStorageTypeChecker::GetPermissionForType(const nsAString& aType, nsACString& aPermissionResult)
 {
   if (!aType.EqualsLiteral(DEVICESTORAGE_PICTURES) &&
       !aType.EqualsLiteral(DEVICESTORAGE_VIDEOS) &&
       !aType.EqualsLiteral(DEVICESTORAGE_MUSIC) &&
       !aType.EqualsLiteral(DEVICESTORAGE_APPS) &&
       !aType.EqualsLiteral(DEVICESTORAGE_SDCARD)) {
@@ -312,16 +440,20 @@ public:
   NS_IMETHOD Run()
   {
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
     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();
     return NS_OK;
   }
 
 private:
   nsRefPtr<DeviceStorageFile> mFile;
   nsCString mType;
 };
 
@@ -757,18 +889,21 @@ DeviceStorageFile::collectFilesInternal(
     } else if (isFile) {
       nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType, mRootDir, newPath);
       aFiles.AppendElement(dsf);
     }
   }
 }
 
 void
-DeviceStorageFile::DirectoryDiskUsage(nsIFile* aFile, uint64_t* aSoFar, const nsAString& aStorageType)
-{
+DeviceStorageFile::DirectoryDiskUsage(nsIFile* aFile,
+                                      uint64_t* aPicturesSoFar,
+                                      uint64_t* aVideosSoFar,
+                                      uint64_t* aMusicSoFar,
+                                      uint64_t* aTotalSoFar) {
   if (!aFile) {
     return;
   }
 
   nsresult rv;
   nsCOMPtr<nsISimpleEnumerator> e;
   rv = aFile->GetDirectoryEntries(getter_AddRefs(e));
 
@@ -803,28 +938,39 @@ 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, aSoFar, aStorageType);
+      DirectoryDiskUsage(f, aPicturesSoFar, aVideosSoFar, aMusicSoFar, aTotalSoFar);
     } else if (isFile) {
 
-      if (!typeChecker->Check(aStorageType, f)) {
-        continue;
-      }
-
       int64_t size;
       rv = f->GetFileSize(&size);
-      if (NS_SUCCEEDED(rv)) {
-        *aSoFar += size;
+      if (NS_FAILED(rv)) {
+        continue;
       }
+      DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet();
+      NS_ASSERTION(typeChecker, "DeviceStorageTypeChecker is null");
+      nsString type;
+      typeChecker->GetTypeFromFile(f, type);
+
+      if (type.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
+        *aPicturesSoFar += size;
+      }
+      else if (type.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
+        *aVideosSoFar += size;
+      }
+      else if (type.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
+        *aMusicSoFar += size;
+      }
+      *aTotalSoFar += size;
     }
   }
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS0(DeviceStorageFile)
 
 #ifdef MOZ_WIDGET_GONK
 nsresult
@@ -1374,17 +1520,17 @@ public:
     }
 
   PostResultEvent(nsRefPtr<DOMRequest>& aRequest, const nsAString & aPath)
     : mPath(aPath)
     {
       mRequest.swap(aRequest);
     }
 
-  PostResultEvent(nsRefPtr<DOMRequest>& aRequest, const int64_t aValue)
+  PostResultEvent(nsRefPtr<DOMRequest>& aRequest, const uint64_t aValue)
     : mValue(aValue)
     {
       mRequest.swap(aRequest);
     }
 
   ~PostResultEvent() {}
 
   NS_IMETHOD Run()
@@ -1406,17 +1552,17 @@ public:
     mRequest->FireSuccess(result);
     mRequest = nullptr;
     return NS_OK;
   }
 
 private:
   nsRefPtr<DeviceStorageFile> mFile;
   nsString mPath;
-  int64_t mValue;
+  uint64_t mValue;
   nsRefPtr<DOMRequest> mRequest;
 };
 
 class WriteFileEvent : public nsRunnable
 {
 public:
   WriteFileEvent(nsIDOMBlob* aBlob,
                  DeviceStorageFile *aFile,
@@ -1536,32 +1682,64 @@ private:
   nsRefPtr<DeviceStorageFile> mFile;
   nsRefPtr<DOMRequest> mRequest;
 };
 
 class UsedSpaceFileEvent : public nsRunnable
 {
 public:
   UsedSpaceFileEvent(DeviceStorageFile* aFile, nsRefPtr<DOMRequest>& aRequest)
-  : mFile(aFile)
-    {
-      mRequest.swap(aRequest);
-    }
+     : mFile(aFile)
+     {
+       mRequest.swap(aRequest);
+     }
 
   ~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);
     nsCOMPtr<nsIRunnable> r;
-    uint64_t diskUsage = 0;
-    DeviceStorageFile::DirectoryDiskUsage(mFile->mFile, &diskUsage, mFile->mStorageType);
-
-    r = new PostResultEvent(mRequest, diskUsage);
+
+    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);
+    } 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);
+      }
+    }
     NS_DispatchToMainThread(r);
+
     return NS_OK;
   }
 
 private:
   nsRefPtr<DeviceStorageFile> mFile;
   nsRefPtr<DOMRequest> mRequest;
 };
 
@@ -1581,17 +1759,17 @@ public:
     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;
     }
 
-    r = new PostResultEvent(mRequest, freeSpace);
+    r = new PostResultEvent(mRequest, static_cast<uint64_t>(freeSpace));
     NS_DispatchToMainThread(r);
     return NS_OK;
   }
 
 private:
   nsRefPtr<DeviceStorageFile> mFile;
   nsRefPtr<DOMRequest> mRequest;
 };
@@ -1830,18 +2008,23 @@ public:
       case DEVICE_STORAGE_REQUEST_USED_SPACE:
       {
         if (XRE_GetProcessType() != GeckoProcessType_Default) {
           PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
           DeviceStorageUsedSpaceParams params(mFile->mStorageType, mFile->mPath);
           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);
-        break;
+        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);
           ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
@@ -1859,16 +2042,17 @@ public:
       }
     }
 
     if (r) {
       nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
       NS_ASSERTION(target, "Must have stream transport service");
       target->Dispatch(r, NS_DISPATCH_NORMAL);
     }
+
     return NS_OK;
   }
 
   bool Recv__delete__(const bool& allow)
   {
     if (allow) {
       Allow();
     }
@@ -2013,22 +2197,32 @@ nsDOMDeviceStorage::GetOrderedVolumeName
   }
 }
 
 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++) {
-    nsresult rv;
     nsRefPtr<nsDOMDeviceStorage> storage = new nsDOMDeviceStorage();
     rv = storage->Init(aWin, aType, volNames[i]);
     if (NS_FAILED(rv)) {
       break;
     }
     aStores.AppendElement(storage);
   }
 }
@@ -2214,16 +2408,19 @@ nsDOMDeviceStorage::FreeSpace(nsIDOMDOMR
                                                      request);
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMDeviceStorage::UsedSpace(nsIDOMDOMRequest** aRetval)
 {
+  DebugOnly<DeviceStorageUsedSpaceCache*> usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet();
+  NS_ASSERTION(usedSpaceCache, "DeviceStorageUsedSpaceCache is null");
+
   nsCOMPtr<nsPIDOMWindow> win = GetOwner();
   if (!win) {
     return NS_ERROR_UNEXPECTED;
   }
 
   nsRefPtr<DOMRequest> request = new DOMRequest(win);
   NS_ADDREF(*aRetval = request);
 
@@ -2412,16 +2609,18 @@ nsDOMDeviceStorage::DispatchMountChangeE
   bool ignore;
   DispatchEvent(ce, &ignore);
 }
 #endif
 
 NS_IMETHODIMP
 nsDOMDeviceStorage::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
 {
+  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;
   }
 
 #ifdef MOZ_WIDGET_GONK
@@ -2448,16 +2647,21 @@ 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;
     }
+
+    DeviceStorageUsedSpaceCache* usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet();
+    NS_ASSERTION(usedSpaceCache, "DeviceStorageUsedSpaceCache is null");
+    usedSpaceCache->Invalidate();
+
     DispatchMountChangeEvent(type);
     return NS_OK;
   }
 #endif
   return NS_OK;
 }
 
 nsresult
--- 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/StaticPtr.h"
 
 namespace mozilla {
 class ErrorResult;
 } // namespace mozilla
 
 #define POST_ERROR_EVENT_FILE_EXISTS                 "NoModificationAllowedError"
 #define POST_ERROR_EVENT_FILE_DOES_NOT_EXIST         "NotFoundError"
 #define POST_ERROR_EVENT_FILE_NOT_ENUMERABLE         "TypeMismatchError"
@@ -46,39 +47,104 @@ enum DeviceStorageRequestType {
     DEVICE_STORAGE_REQUEST_CREATE,
     DEVICE_STORAGE_REQUEST_DELETE,
     DEVICE_STORAGE_REQUEST_WATCH,
     DEVICE_STORAGE_REQUEST_FREE_SPACE,
     DEVICE_STORAGE_REQUEST_USED_SPACE,
     DEVICE_STORAGE_REQUEST_AVAILABLE
 };
 
+class DeviceStorageUsedSpaceCache MOZ_FINAL
+{
+public:
+  static DeviceStorageUsedSpaceCache* CreateOrGet();
+
+  DeviceStorageUsedSpaceCache();
+  ~DeviceStorageUsedSpaceCache();
+
+
+  class InvalidateRunnable MOZ_FINAL : public nsRunnable
+  {
+    public:
+      InvalidateRunnable(DeviceStorageUsedSpaceCache* aCache) {
+        mOwner = aCache;
+      }
+
+      ~InvalidateRunnable() {}
+
+      NS_IMETHOD Run() {
+        mOwner->mDirty = true;
+        return NS_OK;
+      }
+    private:
+      DeviceStorageUsedSpaceCache* mOwner;
+  };
+
+  void Invalidate() {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+    NS_ASSERTION(mIOThread, "Null mIOThread!");
+
+    nsRefPtr<InvalidateRunnable> r = new InvalidateRunnable(this);
+    mIOThread->Dispatch(r, NS_DISPATCH_NORMAL);
+  }
+
+  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,
+                    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);
+
+  bool mDirty;
+  uint64_t mPicturesUsedSize;
+  uint64_t mVideosUsedSize;
+  uint64_t mMusicUsedSize;
+  uint64_t mTotalUsedSize;
+
+  nsCOMPtr<nsIThread> mIOThread;
+
+  static mozilla::StaticAutoPtr<DeviceStorageUsedSpaceCache> sDeviceStorageUsedSpaceCache;
+};
+
 class DeviceStorageTypeChecker MOZ_FINAL
 {
 public:
   static DeviceStorageTypeChecker* CreateOrGet();
 
   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);
 
   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;
   nsString mMusicExtensions;
 
-  static nsAutoPtr<DeviceStorageTypeChecker> sDeviceStorageTypeChecker;
+  static mozilla::StaticAutoPtr<DeviceStorageTypeChecker> sDeviceStorageTypeChecker;
 };
 
 class ContinueCursorEvent MOZ_FINAL : public nsRunnable
 {
 public:
   ContinueCursorEvent(nsRefPtr<mozilla::dom::DOMRequest>& aRequest);
   ContinueCursorEvent(mozilla::dom::DOMRequest* aRequest);
   ~ContinueCursorEvent();