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 130679 b2770cf6dcbd8fddfb9774814743d29e1f6c030b
parent 130678 c10f86bf3439ceba778ac55c3369ab6b266c12ee
child 130680 4a695b132a4b302e2459e239c4dc8b9815a55b6e
push id1579
push userphilringnalda@gmail.com
push dateSat, 04 May 2013 04:38:04 +0000
treeherderfx-team@a56432a42a41 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdylands, dougt, tef
bugs853732
milestone23.0a1
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();