Bug 793955, 802867 - DeviceStorage files returned by DeviceStorage.get() don't always have lastModifiedDate. r=bent. a=blocking-basecamp
authorDoug Turner <dougt@dougt.org>
Thu, 18 Oct 2012 11:29:32 -0700
changeset 110791 cf1bbed46731a5e9992805b931772be3ef3e31d6
parent 110790 3779eb3f036fd4d1e5b7d56d4bc430cb209b6fd7
child 110792 c4f3ea8eec81c9ec98acf2e3c13654c73cbda437
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersbent, blocking-basecamp
bugs793955, 802867
milestone19.0a1
Bug 793955, 802867 - DeviceStorage files returned by DeviceStorage.get() don't always have lastModifiedDate. r=bent. a=blocking-basecamp
content/base/public/nsDOMFile.h
content/base/public/nsIDOMFile.idl
content/base/src/nsDOMFile.cpp
dom/base/StructuredCloneTags.h
dom/devicestorage/DeviceStorageRequestParent.cpp
dom/devicestorage/DeviceStorageRequestParent.h
dom/devicestorage/test/test_basic.html
dom/indexedDB/IDBObjectStore.cpp
dom/ipc/Blob.cpp
dom/ipc/Blob.h
dom/ipc/ContentChild.cpp
dom/ipc/ContentParent.cpp
dom/ipc/DOMTypes.ipdlh
--- a/content/base/public/nsDOMFile.h
+++ b/content/base/public/nsDOMFile.h
@@ -49,54 +49,68 @@ public:
 
   NS_DECL_NSIDOMBLOB
   NS_DECL_NSIDOMFILE
   NS_DECL_NSIXHRSENDABLE
   NS_DECL_NSIMUTABLE
 
   void
   SetLazyData(const nsAString& aName, const nsAString& aContentType,
-              uint64_t aLength)
+              uint64_t aLength, uint64_t aLastModifiedDate)
   {
     NS_ASSERTION(aLength, "must have length");
 
     mName = aName;
     mContentType = aContentType;
     mLength = aLength;
-
+    mLastModificationDate = aLastModifiedDate;
     mIsFile = !aName.IsVoid();
   }
 
   bool IsSizeUnknown() const
   {
     return mLength == UINT64_MAX;
   }
 
+  bool IsDateUnknown() const
+  {
+    return mIsFile && mLastModificationDate == UINT64_MAX;
+  }
+
 protected:
   nsDOMFileBase(const nsAString& aName, const nsAString& aContentType,
+                uint64_t aLength, uint64_t aLastModifiedDate)
+    : mIsFile(true), mImmutable(false), mContentType(aContentType),
+      mName(aName), mStart(0), mLength(aLength), mLastModificationDate(aLastModifiedDate)
+  {
+    // Ensure non-null mContentType by default
+    mContentType.SetIsVoid(false);
+  }
+
+  nsDOMFileBase(const nsAString& aName, const nsAString& aContentType,
                 uint64_t aLength)
     : mIsFile(true), mImmutable(false), mContentType(aContentType),
-      mName(aName), mStart(0), mLength(aLength)
+      mName(aName), mStart(0), mLength(aLength), mLastModificationDate(UINT64_MAX)
   {
     // Ensure non-null mContentType by default
     mContentType.SetIsVoid(false);
   }
 
   nsDOMFileBase(const nsAString& aContentType, uint64_t aLength)
     : mIsFile(false), mImmutable(false), mContentType(aContentType),
-      mStart(0), mLength(aLength)
+      mStart(0), mLength(aLength), mLastModificationDate(UINT64_MAX)
   {
     // Ensure non-null mContentType by default
     mContentType.SetIsVoid(false);
   }
 
   nsDOMFileBase(const nsAString& aContentType, uint64_t aStart,
                 uint64_t aLength)
     : mIsFile(false), mImmutable(false), mContentType(aContentType),
-      mStart(aStart), mLength(aLength)
+      mStart(aStart), mLength(aLength), mLastModificationDate(UINT64_MAX)
   {
     NS_ASSERTION(aLength != UINT64_MAX,
                  "Must know length when creating slice");
     // Ensure non-null mContentType by default
     mContentType.SetIsVoid(false);
   }
 
   virtual ~nsDOMFileBase() {}
@@ -122,30 +136,38 @@ protected:
     NS_ASSERTION(IsStoredFile(), "Should only be called on stored files!");
     NS_ASSERTION(!mFileInfos.IsEmpty(), "Must have at least one file info!");
 
     return mFileInfos.ElementAt(0);
   }
 
   bool mIsFile;
   bool mImmutable;
+
   nsString mContentType;
   nsString mName;
 
   uint64_t mStart;
   uint64_t mLength;
 
+  uint64_t mLastModificationDate;
+
   // Protected by IndexedDatabaseManager::FileMutex()
   nsTArray<nsRefPtr<FileInfo> > mFileInfos;
 };
 
 class nsDOMFile : public nsDOMFileBase
 {
 public:
   nsDOMFile(const nsAString& aName, const nsAString& aContentType,
+            uint64_t aLength, uint64_t aLastModifiedDate)
+  : nsDOMFileBase(aName, aContentType, aLength, aLastModifiedDate)
+  { }
+
+  nsDOMFile(const nsAString& aName, const nsAString& aContentType,
             uint64_t aLength)
   : nsDOMFileBase(aName, aContentType, aLength)
   { }
 
   nsDOMFile(const nsAString& aContentType, uint64_t aLength)
   : nsDOMFileBase(aContentType, aLength)
   { }
 
@@ -178,29 +200,37 @@ public:
 };
 
 class nsDOMFileFile : public nsDOMFile,
                       public nsIJSNativeInitializer
 {
 public:
   // Create as a file
   nsDOMFileFile(nsIFile *aFile)
-    : nsDOMFile(EmptyString(), EmptyString(), UINT64_MAX),
+    : nsDOMFile(EmptyString(), EmptyString(), UINT64_MAX, UINT64_MAX),
       mFile(aFile), mWholeFile(true), mStoredFile(false)
   {
     NS_ASSERTION(mFile, "must have file");
     // Lazily get the content type and size
     mContentType.SetIsVoid(true);
     mFile->GetLeafName(mName);
   }
 
   // Create as a file
   nsDOMFileFile(const nsAString& aName, const nsAString& aContentType,
                 uint64_t aLength, nsIFile *aFile)
-    : nsDOMFile(aName, aContentType, aLength),
+    : nsDOMFile(aName, aContentType, aLength, UINT64_MAX),
+      mFile(aFile), mWholeFile(true), mStoredFile(false)
+  {
+    NS_ASSERTION(mFile, "must have file");
+  }
+
+  nsDOMFileFile(const nsAString& aName, const nsAString& aContentType,
+                uint64_t aLength, nsIFile *aFile, uint64_t aLastModificationDate)
+    : nsDOMFile(aName, aContentType, aLength, aLastModificationDate),
       mFile(aFile), mWholeFile(true), mStoredFile(false)
   {
     NS_ASSERTION(mFile, "must have file");
   }
 
   // Create as a blob
   nsDOMFileFile(nsIFile *aFile, const nsAString& aContentType,
                 nsISupports *aCacheToken)
@@ -208,29 +238,29 @@ public:
       mFile(aFile), mWholeFile(true), mStoredFile(false),
       mCacheToken(aCacheToken)
   {
     NS_ASSERTION(mFile, "must have file");
   }
 
   // Create as a file with custom name
   nsDOMFileFile(nsIFile *aFile, const nsAString& aName)
-    : nsDOMFile(aName, EmptyString(), UINT64_MAX),
+    : nsDOMFile(aName, EmptyString(), UINT64_MAX, UINT64_MAX),
       mFile(aFile), mWholeFile(true), mStoredFile(false)
   {
     NS_ASSERTION(mFile, "must have file");
     // Lazily get the content type and size
     mContentType.SetIsVoid(true);
   }
 
   // Create as a stored file
   nsDOMFileFile(const nsAString& aName, const nsAString& aContentType,
                 uint64_t aLength, nsIFile* aFile,
                 FileInfo* aFileInfo)
-    : nsDOMFile(aName, aContentType, aLength),
+    : nsDOMFile(aName, aContentType, aLength, UINT64_MAX),
       mFile(aFile), mWholeFile(true), mStoredFile(true)
   {
     NS_ASSERTION(mFile, "must have file");
     mFileInfos.AppendElement(aFileInfo);
   }
 
   // Create as a stored blob
   nsDOMFileFile(const nsAString& aContentType, uint64_t aLength,
@@ -239,17 +269,17 @@ public:
       mFile(aFile), mWholeFile(true), mStoredFile(true)
   {
     NS_ASSERTION(mFile, "must have file");
     mFileInfos.AppendElement(aFileInfo);
   }
 
   // Create as a file to be later initialized
   nsDOMFileFile()
-    : nsDOMFile(EmptyString(), EmptyString(), UINT64_MAX),
+    : nsDOMFile(EmptyString(), EmptyString(), UINT64_MAX, UINT64_MAX),
       mWholeFile(true), mStoredFile(false)
   {
     // Lazily get the content type and size
     mContentType.SetIsVoid(true);
     mName.SetIsVoid(true);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
@@ -259,17 +289,18 @@ public:
                         JSContext* aCx,
                         JSObject* aObj,
                         uint32_t aArgc,
                         jsval* aArgv);
 
   // Overrides
   NS_IMETHOD GetSize(uint64_t* aSize);
   NS_IMETHOD GetType(nsAString& aType);
-  NS_IMETHOD GetLastModifiedDate(JSContext* cx, JS::Value *aLastModifiedDate);
+  NS_IMETHOD GetLastModifiedDate(JSContext* cx, JS::Value* aLastModifiedDate);
+  NS_IMETHOD GetMozLastModifiedDate(uint64_t* aLastModifiedDate);
   NS_IMETHOD GetMozFullPathInternal(nsAString& aFullPath);
   NS_IMETHOD GetInternalStream(nsIInputStream**);
 
   // DOMClassInfo constructor (for File("foo"))
   static nsresult
   NewFile(nsISupports* *aNewObject);
 
 protected:
@@ -323,17 +354,17 @@ protected:
 class nsDOMMemoryFile : public nsDOMFile
 {
 public:
   // Create as file
   nsDOMMemoryFile(void *aMemoryBuffer,
                   uint64_t aLength,
                   const nsAString& aName,
                   const nsAString& aContentType)
-    : nsDOMFile(aName, aContentType, aLength),
+    : nsDOMFile(aName, aContentType, aLength, UINT64_MAX),
       mDataOwner(new DataOwner(aMemoryBuffer))
   {
     NS_ASSERTION(mDataOwner && mDataOwner->mData, "must have data");
   }
 
   // Create as blob
   nsDOMMemoryFile(void *aMemoryBuffer,
                   uint64_t aLength,
--- a/content/base/public/nsIDOMFile.idl
+++ b/content/base/public/nsIDOMFile.idl
@@ -23,17 +23,17 @@ class FileManager;
 [ptr] native FileManager(mozilla::dom::indexedDB::FileManager);
 
 interface nsIDOMFileError;
 interface nsIInputStream;
 interface nsIURI;
 interface nsIPrincipal;
 interface nsIDOMBlob;
 
-[scriptable, builtinclass, uuid(16e3f8d1-7f31-48cc-93f5-9c931a977cf6)]
+[scriptable, builtinclass, uuid(52d22585-7737-460e-9731-c658df03304a)]
 interface nsIDOMBlob : nsISupports
 {
   readonly attribute unsigned long long size;
   readonly attribute DOMString type;
 
   [noscript] readonly attribute nsIInputStream internalStream;
   // The caller is responsible for releasing the internalUrl from the
   // blob: protocol handler
@@ -68,9 +68,11 @@ interface nsIDOMFile : nsIDOMBlob
 
   [implicit_jscontext]
   readonly attribute jsval lastModifiedDate;
 
   readonly attribute DOMString mozFullPath;
 
   // This performs no security checks!
   [noscript] readonly attribute DOMString mozFullPathInternal;
+
+  [noscript] readonly attribute uint64_t mozLastModifiedDate;
 };
--- a/content/base/src/nsDOMFile.cpp
+++ b/content/base/src/nsDOMFile.cpp
@@ -169,16 +169,24 @@ nsDOMFileBase::GetSize(uint64_t *aSize)
 
 NS_IMETHODIMP
 nsDOMFileBase::GetType(nsAString &aType)
 {
   aType = mContentType;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDOMFileBase::GetMozLastModifiedDate(uint64_t* aLastModifiedDate)
+{
+  NS_ASSERTION(mIsFile, "Should only be called on files");
+  *aLastModifiedDate = mLastModificationDate;
+  return NS_OK;
+}
+
 // Makes sure that aStart and aEnd is less then or equal to aSize and greater
 // than 0
 static void
 ParseSize(int64_t aSize, int64_t& aStart, int64_t& aEnd)
 {
   CheckedInt64 newStartOffset = aStart;
   if (aStart < -aSize) {
     newStartOffset = 0;
@@ -493,20 +501,30 @@ nsDOMFileFile::NewFile(nsISupports* *aNe
 NS_IMETHODIMP
 nsDOMFileFile::GetMozFullPathInternal(nsAString &aFilename)
 {
   NS_ASSERTION(mIsFile, "Should only be called on files");
   return mFile->GetPath(aFilename);
 }
 
 NS_IMETHODIMP
-nsDOMFileFile::GetLastModifiedDate(JSContext* cx, JS::Value *aLastModifiedDate)
+nsDOMFileFile::GetLastModifiedDate(JSContext* cx, JS::Value* aLastModifiedDate)
 {
+  NS_ASSERTION(mIsFile, "Should only be called on files");
+
   PRTime msecs;
   mFile->GetLastModifiedTime(&msecs);
+  if (IsDateUnknown()) {
+    nsresult rv = mFile->GetLastModifiedTime(&msecs);
+    NS_ENSURE_SUCCESS(rv, rv);
+    mLastModificationDate = msecs;
+  } else {
+    msecs = mLastModificationDate;
+  }
+
   JSObject* date = JS_NewDateObjectMsec(cx, msecs);
   if (date) {
     aLastModifiedDate->setObject(*date);
   }
   else {
     date = JS_NewDateObjectMsec(cx, JS_Now() / PR_USEC_PER_MSEC);
     aLastModifiedDate->setObject(*date);
   }
@@ -557,16 +575,24 @@ nsDOMFileFile::GetType(nsAString &aType)
     mContentType.SetIsVoid(false);
   }
 
   aType = mContentType;
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDOMFileFile::GetMozLastModifiedDate(uint64_t* aLastModifiedDate)
+{
+  NS_ASSERTION(mIsFile, "Should only be called on files");
+  *aLastModifiedDate = mLastModificationDate;
+  return NS_OK;
+}
+
 const uint32_t sFileStreamFlags =
   nsIFileInputStream::CLOSE_ON_EOF |
   nsIFileInputStream::REOPEN_ON_REWIND |
   nsIFileInputStream::DEFER_OPEN;
 
 NS_IMETHODIMP
 nsDOMFileFile::GetInternalStream(nsIInputStream **aStream)
 {
--- a/dom/base/StructuredCloneTags.h
+++ b/dom/base/StructuredCloneTags.h
@@ -5,24 +5,31 @@
 #ifndef StructuredCloneTags_h__
 #define StructuredCloneTags_h__
 
 #include "jsapi.h"
 
 namespace mozilla {
 namespace dom {
 
+// CHANGING THE ORDER/PLACEMENT OF EXISTING ENUM VALUES MAY BREAK INDEXEDDB.
+// PROCEED WITH EXTREME CAUTION.
 enum StructuredCloneTags {
   SCTAG_BASE = JS_SCTAG_USER_MIN,
 
   // These tags are used only for main thread structured clone.
   SCTAG_DOM_BLOB,
-  SCTAG_DOM_FILE,
+
+  // This tag is obsolete and exists only for backwards compatibility with
+  // existing IndexedDB databases.
+  SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE,
+
   SCTAG_DOM_FILELIST,
   SCTAG_DOM_FILEHANDLE,
+  SCTAG_DOM_FILE,
 
   // These tags are used for both main thread and workers.
   SCTAG_DOM_IMAGEDATA,
 
   SCTAG_DOM_MAX
 };
 
 } // namespace dom
--- a/dom/devicestorage/DeviceStorageRequestParent.cpp
+++ b/dom/devicestorage/DeviceStorageRequestParent.cpp
@@ -170,34 +170,36 @@ DeviceStorageRequestParent::PostSuccessE
   SuccessResponse response;
   unused <<  mParent->Send__delete__(mParent, response);
   return NS_OK;
 }
 
 DeviceStorageRequestParent::PostBlobSuccessEvent::PostBlobSuccessEvent(DeviceStorageRequestParent* aParent,
                                                                        DeviceStorageFile* aFile,
                                                                        uint32_t aLength,
-                                                                       nsACString& aMimeType)
+                                                                       nsACString& aMimeType,
+                                                                       uint64_t aLastModifiedDate)
   : CancelableRunnable(aParent)
   , mLength(aLength)
+  , mLastModificationDate(aLastModifiedDate)
   , mFile(aFile)
   , mMimeType(aMimeType)
 {
 }
 
 DeviceStorageRequestParent::PostBlobSuccessEvent::~PostBlobSuccessEvent() {}
 
 nsresult
 DeviceStorageRequestParent::PostBlobSuccessEvent::CancelableRun() {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   nsString mime;
   CopyASCIItoUTF16(mMimeType, mime);
 
-  nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(mFile->mPath, mime, mLength, mFile->mFile);
+  nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(mFile->mPath, mime, mLength, mFile->mFile, mLastModificationDate);
 
   ContentParent* cp = static_cast<ContentParent*>(mParent->Manager());
   BlobParent* actor = cp->GetOrCreateActorForBlob(blob);
 
   BlobResponse response;
   response.blobParent() = actor;
 
   unused <<  mParent->Send__delete__(mParent, response);
@@ -369,17 +371,25 @@ DeviceStorageRequestParent::ReadFileEven
   int64_t fileSize;
   nsresult rv = mFile->mFile->GetFileSize(&fileSize);
   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);
+  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);
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 DeviceStorageRequestParent::EnumerateFileEvent::EnumerateFileEvent(DeviceStorageRequestParent* aParent,
                                                                    DeviceStorageFile* aFile,
                                                                    uint64_t aSince)
   : CancelableRunnable(aParent)
--- a/dom/devicestorage/DeviceStorageRequestParent.h
+++ b/dom/devicestorage/DeviceStorageRequestParent.h
@@ -81,21 +81,22 @@ private:
       PostSuccessEvent(DeviceStorageRequestParent* aParent);
       virtual ~PostSuccessEvent();
       virtual nsresult CancelableRun();
   };
 
   class PostBlobSuccessEvent : public CancelableRunnable
   {
     public:
-      PostBlobSuccessEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile, uint32_t aLength, nsACString& aMimeType);
+      PostBlobSuccessEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile, uint32_t aLength, nsACString& aMimeType, uint64_t aLastModifiedDate);
       virtual ~PostBlobSuccessEvent();
       virtual nsresult CancelableRun();
     private:
       uint32_t mLength;
+      uint64_t mLastModificationDate;
       nsRefPtr<DeviceStorageFile> mFile;
       nsCString mMimeType;
   };
 
   class PostEnumerationSuccessEvent : public CancelableRunnable
   {
     public:
       PostEnumerationSuccessEvent(DeviceStorageRequestParent* aParent, InfallibleTArray<DeviceStorageFileValue>& aPaths);
--- a/dom/devicestorage/test/test_basic.html
+++ b/dom/devicestorage/test/test_basic.html
@@ -56,16 +56,19 @@ function deleteError(e) {
   devicestorage_cleanup();
 }
 
 function getSuccess(e) {
   var storage = navigator.getDeviceStorage("pictures");
   ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
   ok(e.target.result.name == gFileName, "File name should match");
+  ok(e.target.result.size > 0, "File size be greater than zero");
+  ok(e.target.result.type, "File should have a mime type");
+  ok(e.target.result.lastModifiedDate, "File should have a last modified date");
 
   var name = e.target.result.name;
 
   gFileReader.readAsArrayBuffer(gDataBlob);
   gFileReader.onload = function(e) {
     readerCallback(e);
 
     request = storage.delete(name)
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -599,22 +599,24 @@ ActorFromRemoteBlob(nsIDOMBlob* aBlob)
     NS_ASSERTION(actor, "Null actor?!");
     return actor;
   }
   return nullptr;
 }
 
 inline
 bool
-ResolveMysteryBlob(nsIDOMBlob* aBlob, const nsString& aName,
-                   const nsString& aContentType, uint64_t aSize)
+ResolveMysteryFile(nsIDOMBlob* aBlob, const nsString& aName,
+                   const nsString& aContentType, uint64_t aSize,
+                   uint64_t aLastModifiedDate)
 {
   BlobChild* actor = ActorFromRemoteBlob(aBlob);
   if (actor) {
-    return actor->SetMysteryBlobInfo(aName, aContentType, aSize);
+    return actor->SetMysteryBlobInfo(aName, aContentType,
+                                     aSize, aLastModifiedDate);
   }
   return true;
 }
 
 inline
 bool
 ResolveMysteryBlob(nsIDOMBlob* aBlob, const nsString& aContentType,
                    uint64_t aSize)
@@ -1090,17 +1092,28 @@ StructuredCloneReadString(JSStructuredCl
 // static
 JSObject*
 IDBObjectStore::StructuredCloneReadCallback(JSContext* aCx,
                                             JSStructuredCloneReader* aReader,
                                             uint32_t aTag,
                                             uint32_t aData,
                                             void* aClosure)
 {
-  if (aTag == SCTAG_DOM_FILEHANDLE || aTag == SCTAG_DOM_BLOB ||
+  // We need to statically assert that our tag values are what we expect
+  // so that if people accidentally change them they notice.
+  MOZ_STATIC_ASSERT(SCTAG_DOM_BLOB == 0xFFFF8001 &&
+                    SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE == 0xFFFF8002 &&
+                    SCTAG_DOM_FILEHANDLE == 0xFFFF8004 &&
+                    SCTAG_DOM_FILE == 0xFFFF8005,
+                    "You changed our structured clone tag values and just ate "
+                    "everyone's IndexedDB data.  I hope you are happy.");
+
+  if (aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE ||
+      aTag == SCTAG_DOM_FILEHANDLE ||
+      aTag == SCTAG_DOM_BLOB ||
       aTag == SCTAG_DOM_FILE) {
     StructuredCloneReadInfo* cloneReadInfo =
       reinterpret_cast<StructuredCloneReadInfo*>(aClosure);
 
     if (aData >= cloneReadInfo->mFiles.Length()) {
       NS_ERROR("Bad blob index!");
       return nullptr;
     }
@@ -1136,16 +1149,17 @@ IDBObjectStore::StructuredCloneReadCallb
       if (NS_FAILED(rv)) {
         NS_WARNING("Failed to wrap native!");
         return nullptr;
       }
 
       return JSVAL_TO_OBJECT(wrappedFileHandle);
     }
 
+    // If it's not a FileHandle, it's a Blob or a File.
     uint64_t size;
     if (!JS_ReadBytes(aReader, &size, sizeof(uint64_t))) {
       NS_WARNING("Failed to read size!");
       return nullptr;
     }
     size = SwapBytes(size);
 
     nsCString type;
@@ -1191,27 +1205,35 @@ IDBObjectStore::StructuredCloneReadCallb
       if (NS_FAILED(rv)) {
         NS_WARNING("Failed to wrap native!");
         return nullptr;
       }
 
       return JSVAL_TO_OBJECT(wrappedBlob);
     }
 
-    NS_ASSERTION(aTag == SCTAG_DOM_FILE, "Huh?!");
+    NS_ASSERTION(aTag == SCTAG_DOM_FILE ||
+                 aTag == SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE, "Huh?!");
+
+    uint64_t lastModifiedDate = UINT64_MAX;
+    if (aTag != SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE &&
+        !JS_ReadBytes(aReader, &lastModifiedDate, sizeof(lastModifiedDate))) {
+      NS_WARNING("Failed to read lastModifiedDate");
+      return nullptr;
+    }
 
     nsCString name;
     if (!StructuredCloneReadString(aReader, name)) {
       return nullptr;
     }
     NS_ConvertUTF8toUTF16 convName(name);
 
     nsCOMPtr<nsIDOMFile> domFile;
     if (file.mFile) {
-      if (!ResolveMysteryBlob(file.mFile, convName, convType, size)) {
+      if (!ResolveMysteryFile(file.mFile, convName, convType, size, lastModifiedDate)) {
         return nullptr;
       }
       domFile = do_QueryInterface(file.mFile);
       NS_ASSERTION(domFile, "This should never fail!");
     }
     else {
       domFile = new nsDOMFileFile(convName, convType, size, nativeFile,
                                   fileInfo);
@@ -1315,25 +1337,34 @@ IDBObjectStore::StructuredCloneWriteCall
                               cloneWriteInfo->mFiles.Length()) ||
           !JS_WriteBytes(aWriter, &size, sizeof(size)) ||
           !JS_WriteBytes(aWriter, &convTypeLength, sizeof(convTypeLength)) ||
           !JS_WriteBytes(aWriter, convType.get(), convType.Length())) {
         return false;
       }
 
       if (file) {
+        uint64_t lastModifiedDate = 0;
+        if (NS_FAILED(file->GetMozLastModifiedDate(&lastModifiedDate))) {
+          NS_WARNING("Failed to get last modified date!");
+          return false;
+        }
+
+        lastModifiedDate = SwapBytes(lastModifiedDate);
+
         nsString name;
         if (NS_FAILED(file->GetName(name))) {
           NS_WARNING("Failed to get name!");
           return false;
         }
         NS_ConvertUTF16toUTF8 convName(name);
         uint32_t convNameLength = SwapBytes(convName.Length());
 
-        if (!JS_WriteBytes(aWriter, &convNameLength, sizeof(convNameLength)) ||
+        if (!JS_WriteBytes(aWriter, &lastModifiedDate, sizeof(lastModifiedDate)) || 
+            !JS_WriteBytes(aWriter, &convNameLength, sizeof(convNameLength)) ||
             !JS_WriteBytes(aWriter, convName.get(), convName.Length())) {
           return false;
         }
       }
 
       StructuredCloneFile* cloneFile = cloneWriteInfo->mFiles.AppendElement();
       cloneFile->mFile = blob.forget();
       cloneFile->mFileInfo = fileInfo.forget();
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -580,30 +580,38 @@ private:
       }
     }
   };
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
   RemoteBlob(const nsAString& aName, const nsAString& aContentType,
+             uint64_t aLength, uint64_t aModDate)
+  : nsDOMFile(aName, aContentType, aLength, aModDate), mActor(nullptr)
+  {
+    mImmutable = true;
+  }
+
+  RemoteBlob(const nsAString& aName, const nsAString& aContentType,
              uint64_t aLength)
   : nsDOMFile(aName, aContentType, aLength), mActor(nullptr)
   {
     mImmutable = true;
   }
 
   RemoteBlob(const nsAString& aContentType, uint64_t aLength)
   : nsDOMFile(aContentType, aLength), mActor(nullptr)
   {
     mImmutable = true;
   }
 
   RemoteBlob()
-  : nsDOMFile(EmptyString(), EmptyString(), UINT64_MAX), mActor(nullptr)
+  : nsDOMFile(EmptyString(), EmptyString(), UINT64_MAX, UINT64_MAX)
+  , mActor(nullptr)
   {
     mImmutable = true;
   }
 
   virtual ~RemoteBlob()
   {
     if (mActor) {
       mActor->NoteDyingRemoteBlob();
@@ -646,16 +654,31 @@ public:
     return helper->GetStream(aStream);
   }
 
   virtual void*
   GetPBlob() MOZ_OVERRIDE
   {
     return static_cast<typename ActorType::ProtocolType*>(mActor);
   }
+
+  NS_IMETHOD
+  GetLastModifiedDate(JSContext* cx, JS::Value* aLastModifiedDate)
+  {
+    if (IsDateUnknown()) {
+      aLastModifiedDate->setNull();
+    } else {
+      JSObject* date = JS_NewDateObjectMsec(cx, mLastModificationDate);
+      if (!date) {
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
+      aLastModifiedDate->setObject(*date);
+    }
+    return NS_OK;
+  }
 };
 
 template <ActorFlavorEnum ActorFlavor>
 Blob<ActorFlavor>::Blob(nsIDOMBlob* aBlob)
 : mBlob(aBlob), mRemoteBlob(nullptr), mOwnsBlob(true), mBlobIsFile(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aBlob);
@@ -681,17 +704,17 @@ Blob<ActorFlavor>::Blob(const BlobConstr
       break;
     }
 
     case BlobConstructorParams::TFileBlobConstructorParams: {
       const FileBlobConstructorParams& params =
         aParams.get_FileBlobConstructorParams();
       remoteBlob =
         new RemoteBlobType(params.name(), params.contentType(),
-                           params.length());
+                           params.length(), params.modDate());
       mBlobIsFile = true;
       break;
     }
 
     case BlobConstructorParams::TMysteryBlobConstructorParams: {
       remoteBlob = new RemoteBlobType();
       mBlobIsFile = true;
       break;
@@ -765,43 +788,48 @@ Blob<ActorFlavor>::GetBlob()
 
   return blob.forget();
 }
 
 template <ActorFlavorEnum ActorFlavor>
 bool
 Blob<ActorFlavor>::SetMysteryBlobInfo(const nsString& aName,
                                       const nsString& aContentType,
-                                      uint64_t aLength)
+                                      uint64_t aLength,
+                                      uint64_t aLastModifiedDate)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mBlob);
   MOZ_ASSERT(mRemoteBlob);
   MOZ_ASSERT(aLength);
+  MOZ_ASSERT(aLastModifiedDate != UINT64_MAX);
 
-  ToConcreteBlob(mBlob)->SetLazyData(aName, aContentType, aLength);
+  ToConcreteBlob(mBlob)->SetLazyData(aName, aContentType,
+                                     aLength, aLastModifiedDate);
 
-  FileBlobConstructorParams params(aName, aContentType, aLength);
+  FileBlobConstructorParams params(aName, aContentType,
+                                   aLength, aLastModifiedDate);
   return ProtocolType::SendResolveMystery(params);
 }
 
 template <ActorFlavorEnum ActorFlavor>
 bool
 Blob<ActorFlavor>::SetMysteryBlobInfo(const nsString& aContentType,
                                       uint64_t aLength)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mBlob);
   MOZ_ASSERT(mRemoteBlob);
   MOZ_ASSERT(aLength);
 
   nsString voidString;
   voidString.SetIsVoid(true);
 
-  ToConcreteBlob(mBlob)->SetLazyData(voidString, aContentType, aLength);
+  ToConcreteBlob(mBlob)->SetLazyData(voidString, aContentType,
+                                     aLength, UINT64_MAX);
 
   NormalBlobConstructorParams params(aContentType, aLength);
   return ProtocolType::SendResolveMystery(params);
 }
 
 template <ActorFlavorEnum ActorFlavor>
 void
 Blob<ActorFlavor>::SetRemoteBlob(nsRefPtr<RemoteBlobType>& aRemoteBlob)
@@ -885,24 +913,26 @@ Blob<ActorFlavor>::RecvResolveMystery(co
   nsDOMFileBase* blob = ToConcreteBlob(mBlob);
 
   switch (aParams.type()) {
     case ResolveMysteryParams::TNormalBlobConstructorParams: {
       const NormalBlobConstructorParams& params =
         aParams.get_NormalBlobConstructorParams();
       nsString voidString;
       voidString.SetIsVoid(true);
-      blob->SetLazyData(voidString, params.contentType(), params.length());
+      blob->SetLazyData(voidString, params.contentType(),
+                        params.length(), UINT64_MAX);
       break;
     }
 
     case ResolveMysteryParams::TFileBlobConstructorParams: {
       const FileBlobConstructorParams& params =
         aParams.get_FileBlobConstructorParams();
-      blob->SetLazyData(params.name(), params.contentType(), params.length());
+      blob->SetLazyData(params.name(), params.contentType(),
+                        params.length(), params.modDate());
       break;
     }
 
     default:
       MOZ_NOT_REACHED("Unknown params!");
   }
 
   return true;
--- a/dom/ipc/Blob.h
+++ b/dom/ipc/Blob.h
@@ -168,17 +168,17 @@ public:
   // sending side. It may also be called on the receiving side unless this is a
   // "mystery" blob that has not yet received a SetMysteryBlobInfo() call.
   already_AddRefed<nsIDOMBlob>
   GetBlob();
 
   // Use this for files.
   bool
   SetMysteryBlobInfo(const nsString& aName, const nsString& aContentType,
-                     uint64_t aLength);
+                     uint64_t aLength, uint64_t aLastModifiedDate);
 
   // Use this for non-file blobs.
   bool
   SetMysteryBlobInfo(const nsString& aContentType, uint64_t aLength);
 
 private:
   // This constructor is called on the sending side.
   Blob(nsIDOMBlob* aBlob);
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -535,19 +535,21 @@ ContentChild::GetOrCreateActorForBlob(ns
 
   // XXX This is only safe so long as all blob implementations in our tree
   //     inherit nsDOMFileBase. If that ever changes then this will need to grow
   //     a real interface or something.
   const nsDOMFileBase* blob = static_cast<nsDOMFileBase*>(aBlob);
 
   BlobConstructorParams params;
 
-  if (blob->IsSizeUnknown()) {
-    // We don't want to call GetSize yet since that may stat a file on the main
-    // thread here. Instead we'll learn the size lazily from the other process.
+  if (blob->IsSizeUnknown() || blob->IsDateUnknown()) {
+    // We don't want to call GetSize or GetLastModifiedDate
+    // yet since that may stat a file on the main thread
+    // here. Instead we'll learn the size lazily from the
+    // other process.
     params = MysteryBlobConstructorParams();
   }
   else {
     nsString contentType;
     nsresult rv = aBlob->GetType(contentType);
     NS_ENSURE_SUCCESS(rv, nullptr);
 
     uint64_t length;
@@ -556,16 +558,19 @@ ContentChild::GetOrCreateActorForBlob(ns
 
     nsCOMPtr<nsIDOMFile> file = do_QueryInterface(aBlob);
     if (file) {
       FileBlobConstructorParams fileParams;
 
       rv = file->GetName(fileParams.name());
       NS_ENSURE_SUCCESS(rv, nullptr);
 
+      rv = file->GetMozLastModifiedDate(&fileParams.modDate());
+      NS_ENSURE_SUCCESS(rv, nullptr);
+
       fileParams.contentType() = contentType;
       fileParams.length() = length;
 
       params = fileParams;
     } else {
       NormalBlobConstructorParams blobParams;
       blobParams.contentType() = contentType;
       blobParams.length() = length;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1233,34 +1233,39 @@ ContentParent::GetOrCreateActorForBlob(n
 
   // XXX This is only safe so long as all blob implementations in our tree
   //     inherit nsDOMFileBase. If that ever changes then this will need to grow
   //     a real interface or something.
   const nsDOMFileBase* blob = static_cast<nsDOMFileBase*>(aBlob);
 
   BlobConstructorParams params;
 
-  if (blob->IsSizeUnknown()) {
-    // We don't want to call GetSize yet since that may stat a file on the main
-    // thread here. Instead we'll learn the size lazily from the other process.
+  if (blob->IsSizeUnknown() || /*blob->IsDateUnknown()*/ 0) {
+    // We don't want to call GetSize or GetLastModifiedDate
+    // yet since that may stat a file on the main thread
+    // here. Instead we'll learn the size lazily from the
+    // other process.
     params = MysteryBlobConstructorParams();
   }
   else {
     nsString contentType;
     nsresult rv = aBlob->GetType(contentType);
     NS_ENSURE_SUCCESS(rv, nullptr);
 
     uint64_t length;
     rv = aBlob->GetSize(&length);
     NS_ENSURE_SUCCESS(rv, nullptr);
 
     nsCOMPtr<nsIDOMFile> file = do_QueryInterface(aBlob);
     if (file) {
       FileBlobConstructorParams fileParams;
 
+      rv = file->GetMozLastModifiedDate(&fileParams.modDate());
+      NS_ENSURE_SUCCESS(rv, nullptr);
+
       rv = file->GetName(fileParams.name());
       NS_ENSURE_SUCCESS(rv, nullptr);
 
       fileParams.contentType() = contentType;
       fileParams.length() = length;
 
       params = fileParams;
     } else {
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -23,12 +23,13 @@ struct NormalBlobConstructorParams
   uint64_t length;
 };
 
 struct FileBlobConstructorParams
 {
   nsString name;
   nsString contentType;
   uint64_t length;
+  uint64_t modDate;
 };
 
 } // namespace dom
 } // namespace mozilla