Bug 777088 - device Storage - file stat API. r=bent
authorDoug Turner <dougt@dougt.org>
Tue, 31 Jul 2012 12:28:23 -0700
changeset 102018 ef06eb15d5208fe3b2b8b90f9d040dfb7af09c85
parent 102017 3664e5ab11b3981c3eb76cfe60438e64e8a22b93
child 102019 14c4b37f9a0ef49f8e7b708272033978ca29c8c3
push id23261
push useremorley@mozilla.com
push dateFri, 10 Aug 2012 08:25:35 +0000
treeherdermozilla-central@b5ae446888f5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs777088
milestone17.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 777088 - device Storage - file stat API. r=bent
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/devicestorage/DeviceStorageRequestChild.cpp
dom/devicestorage/DeviceStorageRequestParent.cpp
dom/devicestorage/DeviceStorageRequestParent.h
dom/devicestorage/PDeviceStorageRequest.ipdl
dom/devicestorage/nsDeviceStorage.cpp
dom/devicestorage/nsDeviceStorage.h
dom/devicestorage/test/Makefile.in
dom/devicestorage/test/test_stat.html
dom/interfaces/devicestorage/Makefile.in
dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl
dom/interfaces/devicestorage/nsIDOMDeviceStorageStat.idl
dom/ipc/PContent.ipdl
dom/tests/mochitest/general/test_interfaces.html
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -415,16 +415,17 @@
 #include "nsIImageDocument.h"
 
 // Storage includes
 #include "nsDOMStorage.h"
 
 // Device Storage
 #include "nsIDOMDeviceStorage.h"
 #include "nsIDOMDeviceStorageCursor.h"
+#include "nsIDOMDeviceStorageStat.h"
 
 // Drag and drop
 #include "nsIDOMDataTransfer.h"
 
 // Geolocation
 #include "nsIDOMGeoGeolocation.h"
 #include "nsIDOMGeoPosition.h"
 #include "nsIDOMGeoPositionCoords.h"
@@ -1436,16 +1437,19 @@ static nsDOMClassInfoData sClassInfoData
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(DeviceStorage, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(DeviceStorageCursor, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
+  NS_DEFINE_CLASSINFO_DATA(DeviceStorageStat, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
   NS_DEFINE_CLASSINFO_DATA(GeoGeolocation, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   
   NS_DEFINE_CLASSINFO_DATA(GeoPosition, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS) 
   
   NS_DEFINE_CLASSINFO_DATA(GeoPositionCoords, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
@@ -4036,16 +4040,20 @@ nsDOMClassInfo::Init()
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(DeviceStorageCursor, nsIDOMDeviceStorageCursor)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDeviceStorageCursor)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMRequest)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
   DOM_CLASSINFO_MAP_END
 
+  DOM_CLASSINFO_MAP_BEGIN(DeviceStorageStat, nsIDOMDeviceStorageStat)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMDeviceStorageStat)
+  DOM_CLASSINFO_MAP_END
+
   DOM_CLASSINFO_MAP_BEGIN(GeoGeolocation, nsIDOMGeoGeolocation)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMGeoGeolocation)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(GeoPosition, nsIDOMGeoPosition)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMGeoPosition)
   DOM_CLASSINFO_MAP_END
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -381,16 +381,17 @@ DOMCI_CLASS(ModalContentWindow)
 DOMCI_CLASS(DataContainerEvent)
 
 // event used for cross-domain message-passing and for server-sent events in
 // HTML5
 DOMCI_CLASS(MessageEvent)
 
 DOMCI_CLASS(DeviceStorage)
 DOMCI_CLASS(DeviceStorageCursor)
+DOMCI_CLASS(DeviceStorageStat)
 
 // Geolocation
 DOMCI_CLASS(GeoGeolocation)
 DOMCI_CLASS(GeoPosition)
 DOMCI_CLASS(GeoPositionCoords)
 DOMCI_CLASS(GeoPositionError)
 
 DOMCI_CLASS(BatteryManager)
--- a/dom/devicestorage/DeviceStorageRequestChild.cpp
+++ b/dom/devicestorage/DeviceStorageRequestChild.cpp
@@ -50,30 +50,43 @@ DeviceStorageRequestChild::Recv__delete_
     }
 
     case DeviceStorageResponseValue::TBlobResponse:
     {
       BlobResponse r = aValue;
       BlobChild* actor = static_cast<BlobChild*>(r.blobChild());
       nsCOMPtr<nsIDOMBlob> blob = actor->GetBlob();
 
-      jsval result = BlobToJsval(mRequest->GetOwner(), blob);
+      jsval result = InterfaceToJsval(mRequest->GetOwner(), blob, &NS_GET_IID(nsIDOMBlob));
+      mRequest->FireSuccess(result);
+      break;
+    }
+
+    case DeviceStorageResponseValue::TStatStorageResponse:
+    {
+      StatStorageResponse r = aValue;
+
+      nsRefPtr<nsIDOMDeviceStorageStat> domstat = new nsDOMDeviceStorageStat(r.freeBytes(), r.totalBytes());
+      jsval result = InterfaceToJsval(mRequest->GetOwner(), domstat, &NS_GET_IID(nsIDOMDeviceStorageStat));
       mRequest->FireSuccess(result);
       break;
     }
 
     case DeviceStorageResponseValue::TEnumerationResponse:
     {
       EnumerationResponse r = aValue;
       nsDOMDeviceStorageCursor* cursor = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
 
       PRUint32 count = r.paths().Length();
       for (PRUint32 i = 0; i < count; i++) {
         nsCOMPtr<nsIFile> f;
-        NS_NewLocalFile(r.paths()[i].fullpath(), false, getter_AddRefs(f));
+        nsresult rv = NS_NewLocalFile(r.paths()[i].fullpath(), false, getter_AddRefs(f));
+        if (NS_FAILED(rv)) {
+          continue;
+        }
 
         nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
         dsf->SetPath(r.paths()[i].name());
         cursor->mFiles.AppendElement(dsf);
       }
 
       nsCOMPtr<ContinueCursorEvent> event = new ContinueCursorEvent(cursor);
       NS_DispatchToMainThread(event);
--- a/dom/devicestorage/DeviceStorageRequestParent.cpp
+++ b/dom/devicestorage/DeviceStorageRequestParent.cpp
@@ -72,16 +72,32 @@ DeviceStorageRequestParent::DeviceStorag
       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::TDeviceStorageStatParams:
+    {
+      DeviceStorageStatParams p = aParams;
+
+      nsCOMPtr<nsIFile> f;
+      NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
+
+      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
+      nsRefPtr<StatFileEvent> r = new StatFileEvent(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::TDeviceStorageEnumerationParams:
     {
       DeviceStorageEnumerationParams p = aParams;
 
       nsCOMPtr<nsIFile> f;
       NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
 
       nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
@@ -252,17 +268,17 @@ DeviceStorageRequestParent::DeleteFileEv
 
 DeviceStorageRequestParent::DeleteFileEvent::~DeleteFileEvent()
 {
 }
 
 nsresult
 DeviceStorageRequestParent::DeleteFileEvent::CancelableRun()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   mFile->mFile->Remove(true);
 
   nsRefPtr<nsRunnable> r;
 
   bool check = false;
   mFile->mFile->Exists(&check);
   if (check) {
@@ -271,16 +287,46 @@ DeviceStorageRequestParent::DeleteFileEv
   else {
     r = new PostPathResultEvent(mParent, mFile->mPath);
   }
 
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
+DeviceStorageRequestParent::StatFileEvent::StatFileEvent(DeviceStorageRequestParent* aParent,
+                                                         DeviceStorageFile* aFile)
+  : CancelableRunnable(aParent)
+  , mFile(aFile)
+{
+}
+
+DeviceStorageRequestParent::StatFileEvent::~StatFileEvent()
+{
+}
+
+nsresult
+DeviceStorageRequestParent::StatFileEvent::CancelableRun()
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  nsCOMPtr<nsIRunnable> r;
+  PRUint64 diskUsage = DeviceStorageFile::DirectoryDiskUsage(mFile->mFile);
+  PRInt64 freeSpace = 0;
+  nsresult rv = mFile->mFile->GetDiskSpaceAvailable(&freeSpace);
+  if (NS_FAILED(rv)) {
+    r = new PostErrorEvent(mParent, POST_ERROR_EVENT_UNKNOWN);
+    NS_DispatchToMainThread(r);
+    return NS_OK;
+  }
+  r = new PostStatResultEvent(mParent, diskUsage, freeSpace);
+  NS_DispatchToMainThread(r);
+  return NS_OK;
+}
+
 DeviceStorageRequestParent::ReadFileEvent::ReadFileEvent(DeviceStorageRequestParent* aParent,
                                                          DeviceStorageFile* aFile)
   : CancelableRunnable(aParent)
   , mFile(aFile)
 {
   nsCOMPtr<nsIMIMEService> mimeService = do_GetService(NS_MIMESERVICE_CONTRACTID);
   if (mimeService) {
     nsresult rv = mimeService->GetTypeFromFile(mFile->mFile, mMimeType);
@@ -292,17 +338,17 @@ DeviceStorageRequestParent::ReadFileEven
 
 DeviceStorageRequestParent::ReadFileEvent::~ReadFileEvent()
 {
 }
 
 nsresult
 DeviceStorageRequestParent::ReadFileEvent::CancelableRun()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  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);
@@ -333,17 +379,17 @@ DeviceStorageRequestParent::EnumerateFil
 
 DeviceStorageRequestParent::EnumerateFileEvent::~EnumerateFileEvent()
 {
 }
 
 nsresult
 DeviceStorageRequestParent::EnumerateFileEvent::CancelableRun()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  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;
@@ -384,12 +430,35 @@ DeviceStorageRequestParent::PostPathResu
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   SuccessResponse response;
   unused <<  mParent->Send__delete__(mParent, response);
   return NS_OK;
 }
 
+DeviceStorageRequestParent::PostStatResultEvent::PostStatResultEvent(DeviceStorageRequestParent* aParent,
+                                                                     PRInt64 aFreeBytes,
+                                                                     PRInt64 aTotalBytes)
+  : CancelableRunnable(aParent)
+  , mFreeBytes(aFreeBytes)
+  , mTotalBytes(aTotalBytes)
+{
+}
+
+DeviceStorageRequestParent::PostStatResultEvent::~PostStatResultEvent()
+{
+}
+
+nsresult
+DeviceStorageRequestParent::PostStatResultEvent::CancelableRun()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  StatStorageResponse response(mFreeBytes, mTotalBytes);
+  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
@@ -122,16 +122,26 @@ private:
     public:
       DeleteFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
       virtual ~DeleteFileEvent();
       virtual nsresult CancelableRun();
     private:
       nsRefPtr<DeviceStorageFile> mFile;
   };
 
+  class StatFileEvent : public CancelableRunnable
+  {
+    public:
+      StatFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
+      virtual ~StatFileEvent();
+      virtual nsresult CancelableRun();
+     private:
+       nsRefPtr<DeviceStorageFile> mFile;
+   };
+
   class ReadFileEvent : public CancelableRunnable
   {
     public:
       ReadFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
       virtual ~ReadFileEvent();
       virtual nsresult CancelableRun();
     private:
       nsRefPtr<DeviceStorageFile> mFile;
@@ -155,16 +165,28 @@ private:
       PostPathResultEvent(DeviceStorageRequestParent* aParent, const nsAString& aPath);
       virtual ~PostPathResultEvent();
       virtual nsresult CancelableRun();
     private:
       nsRefPtr<DeviceStorageFile> mFile;
       nsString mPath;
   };
 
+  class PostStatResultEvent : public CancelableRunnable
+ {
+    public:
+      PostStatResultEvent(DeviceStorageRequestParent* aParent,
+                          PRInt64 aFreeBytes,
+                          PRInt64 aTotalBytes);
+      virtual ~PostStatResultEvent();
+      virtual nsresult CancelableRun();
+    private:
+      PRInt64 mFreeBytes, mTotalBytes;
+   };
+
 protected:
   void AddRunnable(CancelableRunnable* aRunnable) {
     mRunnables.AppendElement(aRunnable);
   }
   void RemoveRunnable(CancelableRunnable* aRunnable) {
     mRunnables.RemoveElement(aRunnable);
   }
   nsTArray<nsRefPtr<CancelableRunnable> > mRunnables;
--- a/dom/devicestorage/PDeviceStorageRequest.ipdl
+++ b/dom/devicestorage/PDeviceStorageRequest.ipdl
@@ -31,22 +31,29 @@ struct DeviceStorageFileValue
   nsString fullpath;
 };
 
 struct EnumerationResponse
 {
   DeviceStorageFileValue[] paths;
 };
 
+struct StatStorageResponse
+{
+  PRInt64 totalBytes;
+  PRInt64 freeBytes;
+};
+
 union DeviceStorageResponseValue
 {
   ErrorResponse;
   SuccessResponse;
   BlobResponse;
   EnumerationResponse;
+  StatStorageResponse;
 };
 
 sync protocol PDeviceStorageRequest {
     manager PContent;
 child:
     __delete__(DeviceStorageResponseValue response);
 };
 
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -276,16 +276,70 @@ DeviceStorageFile::collectFilesInternal(
     } else if (isFile) {
       nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
       dsf->SetPath(newPath);
       aFiles.AppendElement(dsf);
     }
   }
 }
 
+PRUint64
+DeviceStorageFile::DirectoryDiskUsage(nsIFile* aFile, PRUint64 aSoFar)
+{
+  if (!aFile) {
+    return aSoFar;
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsISimpleEnumerator> e;
+  rv = aFile->GetDirectoryEntries(getter_AddRefs(e));
+
+  if (NS_FAILED(rv) || !e) {
+    return aSoFar;
+  }
+
+  nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
+  NS_ASSERTION(files, "GetDirectoryEntries must return a nsIDirectoryEnumerator");
+
+  nsCOMPtr<nsIFile> f;
+  while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(f))) && f) {
+    bool isDir;
+    rv = f->IsDirectory(&isDir);
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+
+    bool isFile;
+    rv = f->IsFile(&isFile);
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+
+    bool isLink;
+    rv = f->IsSymlink(&isLink);
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+      
+    if (isLink) {
+      // for now, lets just totally ignore symlinks.
+      NS_WARNING("DirectoryDiskUsage ignores symlinks");
+    } else if (isDir) {
+      aSoFar += DirectoryDiskUsage(f, aSoFar);
+    } else if (isFile) {
+      PRInt64 size;
+      rv = f->GetFileSize(&size);
+      if (NS_SUCCEEDED(rv)) {
+	aSoFar += size;
+      }
+    }
+  }
+  return aSoFar;
+}
+
 NS_IMPL_THREADSAFE_ISUPPORTS0(DeviceStorageFile)
 
 #ifdef MOZ_WIDGET_GONK
 static void
 RegisterForSDCardChanges(nsIObserver* aObserver)
 {
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   obs->AddObserver(aObserver, NS_VOLUME_STATE_CHANGED, false);
@@ -354,65 +408,65 @@ nsDOMDeviceStorage::SetRootFileForType(c
   } 
 
 #ifdef MOZ_WIDGET_GONK
   RegisterForSDCardChanges(this);
 #endif
   mFile = f;
 }
 
-static jsval nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(aWindow, "Null Window");
-
-  if (aFile->mEditable) {
-    // TODO - needs janv's file handle support.
-    return JSVAL_NULL;
-  }
-
-  if (aFile == nullptr) {
-    return JSVAL_NULL;
-  }
-
-  nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(aFile->mFile, aFile->mPath);
-  return BlobToJsval(aWindow, blob);
-}
-
-
-jsval BlobToJsval(nsPIDOMWindow* aWindow, nsIDOMBlob* aBlob)
+jsval InterfaceToJsval(nsPIDOMWindow* aWindow, nsISupports* aObject, const nsIID* aIID)
 {
   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
   if (!sgo) {
     return JSVAL_NULL;
   }
 
   nsIScriptContext *scriptContext = sgo->GetScriptContext();
   if (!scriptContext) {
     return JSVAL_NULL;
   }
 
   JSContext *cx = scriptContext->GetNativeContext();
   if (!cx) {
     return JSVAL_NULL;
   }
 
-  jsval wrappedFile;
+  jsval someJsVal;
   nsresult rv = nsContentUtils::WrapNative(cx,
                                            JS_GetGlobalObject(cx),
-                                           aBlob,
-                                           &NS_GET_IID(nsIDOMFile),
-                                           &wrappedFile);
+                                           aObject,
+                                           aIID,
+                                           &someJsVal);
   if (NS_FAILED(rv)) {
     return JSVAL_NULL;
   }
 
-  return wrappedFile;
+  return someJsVal;
 }
 
+jsval nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aWindow, "Null Window");
+
+  if (aFile->mEditable) {
+    // TODO - needs janv's file handle support.
+    return JSVAL_NULL;
+  }
+
+  if (aFile == nullptr) {
+    return JSVAL_NULL;
+  }
+
+  nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(aFile->mFile, aFile->mPath);
+  return InterfaceToJsval(aWindow, blob, &NS_GET_IID(nsIDOMBlob));
+ }
+
+
 jsval StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aWindow, "Null Window");
 
   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
   if (!sgo) {
     return JSVAL_NULL;
@@ -757,16 +811,49 @@ nsDOMDeviceStorageCursor::Recv__delete__
 }
 
 void
 nsDOMDeviceStorageCursor::IPDLRelease()
 {
   Release();
 }
 
+class PostStatResultEvent : public nsRunnable
+{
+public:
+  PostStatResultEvent(nsRefPtr<DOMRequest>& aRequest, PRInt64 aFreeBytes, PRInt64 aTotalBytes)
+    : mFreeBytes(aFreeBytes)
+    , mTotalBytes(aTotalBytes)
+    {
+      mRequest.swap(aRequest);
+    }
+
+  ~PostStatResultEvent() {}
+
+  NS_IMETHOD Run()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+    nsRefPtr<nsIDOMDeviceStorageStat> domstat = new nsDOMDeviceStorageStat(mFreeBytes, mTotalBytes);
+
+    jsval result = InterfaceToJsval(mRequest->GetOwner(),
+				    domstat,
+				    &NS_GET_IID(nsIDOMDeviceStorageStat));
+
+    mRequest->FireSuccess(result);
+    mRequest = nsnull;
+    return NS_OK;
+  }
+
+private:
+  PRInt64 mFreeBytes, mTotalBytes;
+  nsRefPtr<DOMRequest> mRequest;
+};
+
+
 class PostResultEvent : public nsRunnable
 {
 public:
   PostResultEvent(nsRefPtr<DOMRequest>& aRequest, DeviceStorageFile* aFile)
     : mFile(aFile)
     {
       mRequest.swap(aRequest);
     }
@@ -915,28 +1002,63 @@ public:
     return NS_OK;
   }
 
 private:
   nsRefPtr<DeviceStorageFile> mFile;
   nsRefPtr<DOMRequest> mRequest;
 };
 
+class StatFileEvent : public nsRunnable
+{
+public:
+  StatFileEvent(DeviceStorageFile* aFile, nsRefPtr<DOMRequest>& aRequest)
+  : mFile(aFile)
+    {
+      mRequest.swap(aRequest);
+    }
+
+  ~StatFileEvent() {}
+
+  NS_IMETHOD Run()
+  {
+    NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+    nsCOMPtr<nsIRunnable> r;
+    PRUint64 diskUsage = DeviceStorageFile::DirectoryDiskUsage(mFile->mFile);
+    PRInt64 freeSpace = 0;
+    nsresult rv = mFile->mFile->GetDiskSpaceAvailable(&freeSpace);
+    if (NS_FAILED(rv)) {
+      r = new PostErrorEvent(mRequest, POST_ERROR_EVENT_UNKNOWN, mFile);
+      NS_DispatchToMainThread(r);
+      return NS_OK;
+    }
+
+    r = new PostStatResultEvent(mRequest, diskUsage, freeSpace);
+    NS_DispatchToMainThread(r);
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<DeviceStorageFile> mFile;
+  nsRefPtr<DOMRequest> mRequest;
+};
+
 class DeviceStorageRequest MOZ_FINAL
   : public nsIContentPermissionRequest
   , public nsIRunnable
   , public PCOMContentPermissionRequestChild
 {
 public:
 
     enum DeviceStorageRequestType {
         DEVICE_STORAGE_REQUEST_READ,
         DEVICE_STORAGE_REQUEST_WRITE,
         DEVICE_STORAGE_REQUEST_DELETE,
-        DEVICE_STORAGE_REQUEST_WATCH
+        DEVICE_STORAGE_REQUEST_WATCH,
+        DEVICE_STORAGE_REQUEST_STAT
     };
 
     DeviceStorageRequest(const DeviceStorageRequestType aRequestType,
                          nsPIDOMWindow *aWindow,
                          nsIPrincipal *aPrincipal,
                          DeviceStorageFile *aFile,
                          DOMRequest* aRequest,
                          nsDOMDeviceStorage *aDeviceStorage,
@@ -1095,16 +1217,28 @@ public:
           DeviceStorageDeleteParams params(fullpath);
           ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
           return NS_OK;
         }
         r = new DeleteFileEvent(mFile, mRequest);
         break;
       }
 
+      case DEVICE_STORAGE_REQUEST_STAT:
+      {
+        if (XRE_GetProcessType() != GeckoProcessType_Default) {
+          PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
+          DeviceStorageStatParams params(fullpath);
+          ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
+	  return NS_OK;
+        }
+        r = new StatFileEvent(mFile, mRequest);
+        break;
+      }
+
       case DEVICE_STORAGE_REQUEST_WATCH:
       {
          if (XRE_GetProcessType() != GeckoProcessType_Default) {
            nsString fullpath;
            mFile->mFile->GetPath(fullpath);
            nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
            obs->AddObserver(mDeviceStorage, "file-watcher-update", false);
            ContentChild::GetSingleton()->SendAddFileWatch(fullpath);
@@ -1406,16 +1540,37 @@ nsDOMDeviceStorage::Delete(const JS::Val
     r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_DELETE,
                                  win, mPrincipal, dsf, request);
   }
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMDeviceStorage::Stat(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(mFile);
+  nsCOMPtr<nsIRunnable> r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_STAT,
+						     win,
+						     mPrincipal,
+						     dsf,
+						     request);
+  NS_DispatchToMainThread(r);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMDeviceStorage::Enumerate(const JS::Value & aName,
                              const JS::Value & aOptions,
                              JSContext* aCx,
                              PRUint8 aArgc,
                              nsIDOMDeviceStorageCursor** aRetval)
 {
   return EnumerateInternal(aName, aOptions, aCx, aArgc, false, aRetval);
 }
@@ -1542,16 +1697,51 @@ nsDOMDeviceStorage::DispatchMountChangeE
     return;
   }
 
   bool ignore;
   DispatchEvent(ce, &ignore);
 }
 #endif
 
+DOMCI_DATA(DeviceStorageStat, nsDOMDeviceStorageStat)
+
+NS_INTERFACE_MAP_BEGIN(nsDOMDeviceStorageStat)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMDeviceStorageStat)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(DeviceStorageStat)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsDOMDeviceStorageStat)
+NS_IMPL_RELEASE(nsDOMDeviceStorageStat)
+
+nsDOMDeviceStorageStat::nsDOMDeviceStorageStat(PRUint64 aFreeBytes, PRUint64 aTotalBytes)
+  : mFreeBytes(aFreeBytes)
+  , mTotalBytes(aTotalBytes)
+{
+}
+
+nsDOMDeviceStorageStat::~nsDOMDeviceStorageStat()
+{
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorageStat::GetTotalBytes(PRUint64 *aTotalBytes)
+{
+  *aTotalBytes = mTotalBytes;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorageStat::GetFreeBytes(PRUint64 *aFreeBytes)
+{
+  *aFreeBytes = mFreeBytes;
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 nsDOMDeviceStorage::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
 {
   if (!strcmp(aTopic, "file-watcher-update")) {
 
     // data strings will have the format of
     //  reason:path
     nsDependentString data(aData);
--- a/dom/devicestorage/nsDeviceStorage.h
+++ b/dom/devicestorage/nsDeviceStorage.h
@@ -10,16 +10,17 @@ class nsPIDOMWindow;
 
 #include "DOMRequest.h"
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDOMClassInfoID.h"
 #include "nsIClassInfo.h"
 #include "nsIContentPermissionPrompt.h"
 #include "nsIDOMDeviceStorageCursor.h"
+#include "nsIDOMDeviceStorageStat.h"
 #include "nsIDOMWindow.h"
 #include "nsIURI.h"
 #include "nsInterfaceHashtable.h"
 #include "nsIPrincipal.h"
 #include "nsString.h"
 #include "nsWeakPtr.h"
 #include "nsIDOMEventListener.h"
 #include "nsIDOMEventTarget.h"
@@ -29,20 +30,22 @@ class nsPIDOMWindow;
 
 
 #define POST_ERROR_EVENT_FILE_DOES_NOT_EXIST         "File location doesn't exists"
 #define POST_ERROR_EVENT_FILE_NOT_ENUMERABLE         "File location is not enumerable"
 #define POST_ERROR_EVENT_PERMISSION_DENIED           "Permission Denied"
 #define POST_ERROR_EVENT_ILLEGAL_FILE_NAME           "Illegal file name"
 #define POST_ERROR_EVENT_UNKNOWN                     "Unknown"
 #define POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED "Non-string type unsupported"
+#define POST_ERROR_EVENT_NOT_IMPLEMENTED             "Not implemented"
 
 using namespace mozilla::dom;
 
-class DeviceStorageFile MOZ_FINAL : public nsISupports {
+class DeviceStorageFile MOZ_FINAL
+  : public nsISupports {
 public:
   nsCOMPtr<nsIFile> mFile;
   nsString mPath;
   bool mEditable;
 
   DeviceStorageFile(nsIFile* aFile, const nsAString& aPath);
   DeviceStorageFile(nsIFile* aFile);
   void SetPath(const nsAString& aPath);
@@ -54,16 +57,18 @@ public:
   // outside of the type of storage the user asked for.
   bool IsSafePath();
 
   nsresult Write(nsIInputStream* aInputStream);
   nsresult Write(InfallibleTArray<PRUint8>& bits);
   void CollectFiles(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles, PRUint64 aSince = 0);
   void collectFilesInternal(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles, PRUint64 aSince, nsAString& aRootPath);
 
+  static PRUint64 DirectoryDiskUsage(nsIFile* aFile, PRUint64 aSoFar = 0);
+
 private:
   void NormalizeFilePath();
   void AppendRelativePath();
 };
 
 class ContinueCursorEvent MOZ_FINAL: public nsRunnable
 {
 public:
@@ -101,15 +106,29 @@ public:
 
 private:
   ~nsDOMDeviceStorageCursor();
 
   nsRefPtr<DeviceStorageFile> mFile;
   nsCOMPtr<nsIPrincipal> mPrincipal;
 };
 
+class nsDOMDeviceStorageStat MOZ_FINAL
+  : public nsIDOMDeviceStorageStat
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMDEVICESTORAGESTAT
+
+  nsDOMDeviceStorageStat(PRUint64 aFreeBytes, PRUint64 aTotalBytes);
+
+private:
+  ~nsDOMDeviceStorageStat();
+  PRUint64 mFreeBytes, mTotalBytes;
+};
+
 //helpers
 jsval StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString);
-jsval nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile, bool aEditable);
-jsval BlobToJsval(nsPIDOMWindow* aWindow, nsIDOMBlob* aBlob);
+jsval nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile);
+jsval InterfaceToJsval(nsPIDOMWindow* aWindow, nsISupports* aObject, const nsIID* aIID);
 
 
 #endif
--- a/dom/devicestorage/test/Makefile.in
+++ b/dom/devicestorage/test/Makefile.in
@@ -14,12 +14,13 @@ MOCHITEST_FILES	= \
 		test_sanity.html \
 		test_basic.html \
 		test_enumerate.html \
 		test_enumerateMultipleContinue.html \
 		test_overwrite.html \
 		test_dotdot.html \
 		test_enumerateOptions.html \
 		test_lastModificationFilter.html \
+		test_stat.html \
 		devicestorage_common.js \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/test/test_stat.html
@@ -0,0 +1,72 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html> <!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=717103
+-->
+<head>
+  <title>Test for the device storage API </title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="devicestorage_common.js"></script>
+
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+devicestorage_setup();
+
+function statSuccess(e) {
+  ok(e.target.result.freeBytes  > 0, "free bytes should exist and be greater than zero");
+  ok(e.target.result.totalBytes > 0, "total bytes should exist and be greater than zero");
+  devicestorage_cleanup();
+}
+
+function statError(e) {
+  ok(false, "statError was called");
+  devicestorage_cleanup();
+}
+
+var isMac = /Mac/.test(navigator.platform);
+var isWin = /Win/.test(navigator.platform);
+
+if (isMac || isWin) {
+  todo(false, "stat is not available on mac or windows yet. see bug xxxx");
+  devicestorage_cleanup();
+} else {
+  var storage = navigator.getDeviceStorage("testing");
+  ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
+
+
+  function addError(e) {
+    ok(false, "addError was called : " + e.target.error.name);
+    devicestorage_cleanup();
+  }
+
+  function addSuccess(e) {
+    request = storage.stat();
+    ok(request, "Should have a non-null request");
+
+    request.onsuccess = statSuccess;
+    request.onerror = statError;
+  }
+
+  request = storage.addNamed(createRandomBlob(), "a/b");
+  request.onsuccess = addSuccess;
+  request.onerror = addError;
+
+}
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/interfaces/devicestorage/Makefile.in
+++ b/dom/interfaces/devicestorage/Makefile.in
@@ -14,14 +14,15 @@ LIBRARY_NAME     = domdevicestorage_s
 XPIDL_MODULE     = dom_devicestorage
 LIBXUL_LIBRARY   = 1
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/dom/dom-config.mk
 
 XPIDLSRCS = \
 	nsIDOMDeviceStorage.idl \
+	nsIDOMDeviceStorageStat.idl \
 	nsIDOMDeviceStorageCursor.idl \
 	nsIDOMNavigatorDeviceStorage.idl \
 	nsIDOMDeviceStorageChangeEvent.idl
 
 include $(topsrcdir)/config/rules.mk
 
--- a/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl
+++ b/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl
@@ -10,17 +10,17 @@ interface nsIDOMDeviceStorageCursor;
 interface nsIDOMDeviceStorageChangeEvent;
 interface nsIDOMEventListener;
 
 dictionary DeviceStorageEnumerationParameters
 {
   jsval since;
 };
 
-[scriptable, uuid(3dbe0137-ca73-44c5-bcde-25c297bf7c65), builtinclass]
+[scriptable, uuid(36f3b16b-a398-4b19-944e-ce299b714725), builtinclass]
 interface nsIDOMDeviceStorage : nsIDOMEventTarget
 {
     attribute nsIDOMEventListener onchange;
     nsIDOMDOMRequest add(in nsIDOMBlob aBlob);
     nsIDOMDOMRequest addNamed(in nsIDOMBlob aBlob, in DOMString aName);
 
     [implicit_jscontext]
     nsIDOMDOMRequest get(in jsval aName);
@@ -31,9 +31,11 @@ interface nsIDOMDeviceStorage : nsIDOMEv
     [implicit_jscontext]
     nsIDOMDOMRequest delete(in jsval aName);
 
     [optional_argc, implicit_jscontext]
     nsIDOMDeviceStorageCursor enumerate([optional] in jsval aName, /* DeviceStorageEnumerationParameters */ [optional] in jsval options);
 
     [optional_argc, implicit_jscontext]
     nsIDOMDeviceStorageCursor enumerateEditable([optional] in jsval aName, /* DeviceStorageEnumerationParameters */ [optional] in jsval options);
+
+    nsIDOMDOMRequest stat();
 };
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/devicestorage/nsIDOMDeviceStorageStat.idl
@@ -0,0 +1,12 @@
+/* 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 "domstubs.idl"
+
+[scriptable, uuid(b951ec07-d5db-42fc-bf4c-4eded202f7f5)]
+interface nsIDOMDeviceStorageStat : nsISupports
+{
+  readonly attribute PRUint64 totalBytes;
+  readonly attribute PRUint64 freeBytes;
+};
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -66,16 +66,21 @@ struct FontListEntry {
     nsString  faceName;
     nsCString filepath;
     PRUint16  weight;
     PRInt16   stretch;
     PRUint8   italic;
     PRUint8   index;
 };
 
+struct DeviceStorageStatParams
+{
+  nsString fullpath;
+};
+
 struct DeviceStorageAddParams
 {
   PBlob blob;
   nsString name;
   nsString fullpath;
 };
 
 struct DeviceStorageGetParams
@@ -96,16 +101,17 @@ struct DeviceStorageEnumerationParams
 };
 
 union DeviceStorageParams
 {
   DeviceStorageAddParams;
   DeviceStorageGetParams;
   DeviceStorageDeleteParams;
   DeviceStorageEnumerationParams;
+  DeviceStorageStatParams;
 };
 
 struct SlicedBlobConstructorParams
 {
   PBlob source;
   uint64_t begin;
   uint64_t end;
   nsString contentType;
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -65,16 +65,17 @@ var interfaceNamesInGlobalScope =
     "Pkcs11",
     "NotifyAudioAvailableEvent",
     "Array",
     "SVGZoomAndPan",
     "XULPopupElement",
     "MediaError",
     "DeviceStorageCursor",
     "DeviceStorageChangeEvent",
+    "DeviceStorageStat",
     "PageTransitionEvent",
     "DataContainerEvent",
     "MozCSSKeyframesRule",
     "SVGAnimatedInteger",
     "TouchEvent",
     "OpenWindowEventDetail",
     "IDBIndex",
     "EventListener",