Bug 1177688, part 1 - Add API and functionality to the BlobImpl classes so that BlobImpl's that are created from an nsIFile can provide information about whether or not the nsIFile was a directory. r=baku
authorJonathan Watt <jwatt@jwatt.org>
Tue, 23 Jun 2015 00:31:28 +0100
changeset 251267 e53abe7d9acc05f3b6fbc7e0a711ca982c7e6b06
parent 251266 97a71b167f2a87c6a53dfc96adcc367f0af6d738
child 251268 8e404e27bd8a9b600c2525999124d3bcd3bbf303
push id28991
push usercbook@mozilla.com
push dateFri, 03 Jul 2015 10:08:14 +0000
treeherdermozilla-central@b6a79816ee71 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1177688
milestone42.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 1177688, part 1 - Add API and functionality to the BlobImpl classes so that BlobImpl's that are created from an nsIFile can provide information about whether or not the nsIFile was a directory. r=baku
dom/base/File.cpp
dom/base/File.h
dom/indexedDB/FileSnapshot.cpp
dom/indexedDB/IDBObjectStore.cpp
dom/ipc/Blob.cpp
dom/ipc/BlobChild.h
dom/ipc/DOMTypes.ipdlh
--- a/dom/base/File.cpp
+++ b/dom/base/File.cpp
@@ -224,16 +224,22 @@ Blob::Blob(nsISupports* aParent, BlobImp
 }
 
 bool
 Blob::IsFile() const
 {
   return mImpl->IsFile();
 }
 
+bool
+Blob::IsDirectory() const
+{
+  return mImpl->IsDirectory();
+}
+
 const nsTArray<nsRefPtr<BlobImpl>>*
 Blob::GetSubBlobImpls() const
 {
   return mImpl->GetSubBlobImpls();
 }
 
 already_AddRefed<File>
 Blob::ToFile()
@@ -416,20 +422,21 @@ File::Create(nsISupports* aParent, BlobI
   MOZ_ASSERT(aImpl->IsFile());
 
   return new File(aParent, aImpl);
 }
 
 /* static */ already_AddRefed<File>
 File::Create(nsISupports* aParent, const nsAString& aName,
              const nsAString& aContentType, uint64_t aLength,
-             int64_t aLastModifiedDate)
+             int64_t aLastModifiedDate, BlobDirState aDirState)
 {
   nsRefPtr<File> file = new File(aParent,
-    new BlobImplBase(aName, aContentType, aLength, aLastModifiedDate));
+    new BlobImplBase(aName, aContentType, aLength, aLastModifiedDate,
+                     aDirState));
   return file.forget();
 }
 
 /* static */ already_AddRefed<File>
 File::Create(nsISupports* aParent, const nsAString& aName,
              const nsAString& aContentType, uint64_t aLength)
 {
   nsRefPtr<File> file = new File(aParent,
@@ -1051,16 +1058,27 @@ void
 BlobImplFile::SetPath(const nsAString& aPath)
 {
   MOZ_ASSERT(aPath.IsEmpty() ||
              aPath[aPath.Length() - 1] == char16_t('/'),
              "Path must end with a path separator");
   mPath = aPath;
 }
 
+void
+BlobImplFile::LookupAndCacheIsDirectory()
+{
+  MOZ_ASSERT(mIsFile,
+             "This should only be called when this object has been created "
+             "from an nsIFile to note that the nsIFile is a directory");
+  bool isDir;
+  mFile->IsDirectory(&isDir);
+  mDirState = isDir ? BlobDirState::eIsDir : BlobDirState::eIsNotDir;
+}
+
 ////////////////////////////////////////////////////////////////////////////
 // BlobImplMemory implementation
 
 NS_IMPL_ISUPPORTS_INHERITED0(BlobImplMemory, BlobImpl)
 
 already_AddRefed<BlobImpl>
 BlobImplMemory::CreateSlice(uint64_t aStart, uint64_t aLength,
                             const nsAString& aContentType,
--- a/dom/base/File.h
+++ b/dom/base/File.h
@@ -46,16 +46,28 @@ class FileInfo;
 
 struct BlobPropertyBag;
 struct ChromeFilePropertyBag;
 struct FilePropertyBag;
 class BlobImpl;
 class File;
 class OwningArrayBufferOrArrayBufferViewOrBlobOrString;
 
+/**
+ * Used to indicate when a Blob/BlobImpl that was created from an nsIFile
+ * (when IsFile() will return true) was from an nsIFile for which
+ * nsIFile::IsDirectory() returned true. This is a tri-state to enable us to
+ * assert that the state is always set when callers request it.
+ */
+enum BlobDirState : uint32_t {
+  eIsDir,
+  eIsNotDir,
+  eUnknownIfDir
+};
+
 class Blob : public nsIDOMBlob
            , public nsIXHRSendable
            , public nsIMutable
            , public nsSupportsWeakReference
            , public nsWrapperCache
 {
 public:
   NS_DECL_NSIDOMBLOB
@@ -90,23 +102,32 @@ public:
 
   BlobImpl* Impl() const
   {
     return mImpl;
   }
 
   bool IsFile() const;
 
+  /**
+   * This may return true if the Blob was created from an nsIFile that is a
+   * directory.
+   */
+  bool IsDirectory() const;
+
   const nsTArray<nsRefPtr<BlobImpl>>* GetSubBlobImpls() const;
 
   // This method returns null if this Blob is not a File; it returns
   // the same object in case this Blob already implements the File interface;
   // otherwise it returns a new File object with the same BlobImpl.
   already_AddRefed<File> ToFile();
 
+  // XXXjwatt Consider having a ToDirectory() method. The need for a FileSystem
+  // object complicates that though.
+
   // This method creates a new File object with the given name and the same
   // BlobImpl.
   already_AddRefed<File> ToFile(const nsAString& aName) const;
 
   already_AddRefed<Blob>
   CreateSlice(uint64_t aStart, uint64_t aLength, const nsAString& aContentType,
               ErrorResult& aRv);
 
@@ -179,17 +200,17 @@ public:
   // Note: BlobImpl must be a File in order to use this method.
   // Check impl->IsFile().
   static File*
   Create(nsISupports* aParent, BlobImpl* aImpl);
 
   static already_AddRefed<File>
   Create(nsISupports* aParent, const nsAString& aName,
          const nsAString& aContentType, uint64_t aLength,
-         int64_t aLastModifiedDate);
+         int64_t aLastModifiedDate, BlobDirState aDirState);
 
   static already_AddRefed<File>
   Create(nsISupports* aParent, const nsAString& aName,
          const nsAString& aContentType, uint64_t aLength);
 
   // The returned File takes ownership of aMemoryBuffer. aMemoryBuffer will be
   // freed by free so it must be allocated by malloc or something
   // compatible with it.
@@ -337,88 +358,115 @@ public:
 
   virtual nsresult GetMutable(bool* aMutable) const = 0;
 
   virtual nsresult SetMutable(bool aMutable) = 0;
 
   virtual void SetLazyData(const nsAString& aName,
                            const nsAString& aContentType,
                            uint64_t aLength,
-                           int64_t aLastModifiedDate) = 0;
+                           int64_t aLastModifiedDate,
+                           BlobDirState aDirState) = 0;
 
   virtual bool IsMemoryFile() const = 0;
 
   virtual bool IsSizeUnknown() const = 0;
 
   virtual bool IsDateUnknown() const = 0;
 
   virtual bool IsFile() const = 0;
 
+  /**
+   * Called when this BlobImpl was created from an nsIFile in order to call
+   * nsIFile::IsDirectory() and cache the result so that when the BlobImpl is
+   * copied to another process that informaton is available.
+   * nsIFile::IsDirectory() does synchronous I/O, and BlobImpl objects may be
+   * created on the main thread or in a non-chrome process (where I/O is not
+   * allowed). Do not call this on a non-chrome process, and preferably do not
+   * call it on the main thread.
+   *
+   * Not all creators of BlobImplFile will call this method, in which case
+   * calling IsDirectory will MOZ_ASSERT.
+   */
+  virtual void LookupAndCacheIsDirectory() = 0;
+  virtual bool IsDirectory() const = 0;
+
+  /**
+   * Prefer IsDirectory(). This exists to help consumer code pass on state from
+   * one BlobImpl when creating another.
+   */
+  virtual BlobDirState GetDirState() const = 0;
+
   // True if this implementation can be sent to other threads.
   virtual bool MayBeClonedToOtherThreads() const
   {
     return true;
   }
 
 protected:
   virtual ~BlobImpl() {}
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(BlobImpl, FILEIMPL_IID)
 
 class BlobImplBase : public BlobImpl
 {
 public:
   BlobImplBase(const nsAString& aName, const nsAString& aContentType,
-               uint64_t aLength, int64_t aLastModifiedDate)
+               uint64_t aLength, int64_t aLastModifiedDate,
+               BlobDirState aDirState)
     : mIsFile(true)
     , mImmutable(false)
+    , mDirState(aDirState)
     , mContentType(aContentType)
     , mName(aName)
     , mStart(0)
     , mLength(aLength)
     , mLastModificationDate(aLastModifiedDate)
     , mSerialNumber(NextSerialNumber())
   {
     // Ensure non-null mContentType by default
     mContentType.SetIsVoid(false);
   }
 
   BlobImplBase(const nsAString& aName, const nsAString& aContentType,
                uint64_t aLength)
     : mIsFile(true)
     , mImmutable(false)
+    , mDirState(BlobDirState::eUnknownIfDir)
     , mContentType(aContentType)
     , mName(aName)
     , mStart(0)
     , mLength(aLength)
     , mLastModificationDate(INT64_MAX)
     , mSerialNumber(NextSerialNumber())
   {
     // Ensure non-null mContentType by default
     mContentType.SetIsVoid(false);
   }
 
   BlobImplBase(const nsAString& aContentType, uint64_t aLength)
     : mIsFile(false)
     , mImmutable(false)
+    , mDirState(BlobDirState::eUnknownIfDir)
     , mContentType(aContentType)
     , mStart(0)
     , mLength(aLength)
     , mLastModificationDate(INT64_MAX)
     , mSerialNumber(NextSerialNumber())
   {
     // Ensure non-null mContentType by default
     mContentType.SetIsVoid(false);
   }
 
   BlobImplBase(const nsAString& aContentType, uint64_t aStart,
                uint64_t aLength)
     : mIsFile(false)
     , mImmutable(false)
+    , mDirState(BlobDirState::eUnknownIfDir)
     , mContentType(aContentType)
     , mStart(aStart)
     , mLength(aLength)
     , mLastModificationDate(INT64_MAX)
     , mSerialNumber(NextSerialNumber())
   {
     NS_ASSERTION(aLength != UINT64_MAX,
                  "Must know length when creating slice");
@@ -480,17 +528,18 @@ public:
                                nsACString& aCharset) override;
 
   virtual nsresult GetMutable(bool* aMutable) const override;
 
   virtual nsresult SetMutable(bool aMutable) override;
 
   virtual void
   SetLazyData(const nsAString& aName, const nsAString& aContentType,
-              uint64_t aLength, int64_t aLastModifiedDate) override
+              uint64_t aLength, int64_t aLastModifiedDate,
+              BlobDirState aDirState) override
   {
     NS_ASSERTION(aLength, "must have length");
 
     mName = aName;
     mContentType = aContentType;
     mLength = aLength;
     mLastModificationDate = aLastModifiedDate;
     mIsFile = !aName.IsVoid();
@@ -506,16 +555,37 @@ public:
     return mIsFile && mLastModificationDate == INT64_MAX;
   }
 
   virtual bool IsFile() const override
   {
     return mIsFile;
   }
 
+  virtual void LookupAndCacheIsDirectory() override
+  {
+    MOZ_ASSERT(false, "Why is this being called on a non-BlobImplFile?");
+  }
+
+  /**
+   * Returns true if the nsIFile that this object wraps is a directory.
+   */
+  virtual bool IsDirectory() const override
+  {
+    MOZ_ASSERT(mDirState != BlobDirState::eUnknownIfDir,
+               "Must only be used by callers for whom the code paths are "
+               "know to call LookupAndCacheIsDirectory()");
+    return mDirState == BlobDirState::eIsDir;
+  }
+
+  virtual BlobDirState GetDirState() const override
+  {
+    return mDirState;
+  }
+
   virtual bool IsStoredFile() const
   {
     return false;
   }
 
   virtual bool IsWholeFile() const
   {
     NS_NOTREACHED("Should only be called on dom blobs backed by files!");
@@ -547,16 +617,17 @@ 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;
+  BlobDirState mDirState;
 
   nsString mContentType;
   nsString mName;
   nsString mPath; // The path relative to a directory chosen by the user
 
   uint64_t mStart;
   uint64_t mLength;
 
@@ -574,17 +645,18 @@ protected:
  */
 class BlobImplMemory final : public BlobImplBase
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
   BlobImplMemory(void* aMemoryBuffer, uint64_t aLength, const nsAString& aName,
                  const nsAString& aContentType, int64_t aLastModifiedDate)
-    : BlobImplBase(aName, aContentType, aLength, aLastModifiedDate)
+    : BlobImplBase(aName, aContentType, aLength, aLastModifiedDate,
+                   BlobDirState::eIsNotDir)
     , mDataOwner(new DataOwner(aMemoryBuffer, aLength))
   {
     NS_ASSERTION(mDataOwner && mDataOwner->mData, "must have data");
   }
 
   BlobImplMemory(void* aMemoryBuffer, uint64_t aLength,
                  const nsAString& aContentType)
     : BlobImplBase(aContentType, aLength)
@@ -704,30 +776,32 @@ private:
 
 class BlobImplFile : public BlobImplBase
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
   // Create as a file
   explicit BlobImplFile(nsIFile* aFile, bool aTemporary = false)
-    : BlobImplBase(EmptyString(), EmptyString(), UINT64_MAX, INT64_MAX)
+    : BlobImplBase(EmptyString(), EmptyString(), UINT64_MAX, INT64_MAX,
+                   BlobDirState::eUnknownIfDir)
     , mFile(aFile)
     , mWholeFile(true)
     , mStoredFile(false)
     , mIsTemporary(aTemporary)
   {
     NS_ASSERTION(mFile, "must have file");
     // Lazily get the content type and size
     mContentType.SetIsVoid(true);
     mFile->GetLeafName(mName);
   }
 
   BlobImplFile(nsIFile* aFile, indexedDB::FileInfo* aFileInfo)
-    : BlobImplBase(EmptyString(), EmptyString(), UINT64_MAX, INT64_MAX)
+    : BlobImplBase(EmptyString(), EmptyString(), UINT64_MAX, INT64_MAX,
+                   BlobDirState::eUnknownIfDir)
     , mFile(aFile)
     , mWholeFile(true)
     , mStoredFile(true)
     , mIsTemporary(false)
   {
     NS_ASSERTION(mFile, "must have file");
     NS_ASSERTION(aFileInfo, "must have file info");
     // Lazily get the content type and size
@@ -735,58 +809,62 @@ public:
     mFile->GetLeafName(mName);
 
     mFileInfos.AppendElement(aFileInfo);
   }
 
   // Create as a file
   BlobImplFile(const nsAString& aName, const nsAString& aContentType,
                uint64_t aLength, nsIFile* aFile)
-    : BlobImplBase(aName, aContentType, aLength, UINT64_MAX)
+    : BlobImplBase(aName, aContentType, aLength, UINT64_MAX,
+                   BlobDirState::eUnknownIfDir)
     , mFile(aFile)
     , mWholeFile(true)
     , mStoredFile(false)
     , mIsTemporary(false)
   {
     NS_ASSERTION(mFile, "must have file");
   }
 
   BlobImplFile(const nsAString& aName, const nsAString& aContentType,
                uint64_t aLength, nsIFile* aFile,
                int64_t aLastModificationDate)
-    : BlobImplBase(aName, aContentType, aLength, aLastModificationDate)
+    : BlobImplBase(aName, aContentType, aLength, aLastModificationDate,
+                   BlobDirState::eUnknownIfDir)
     , mFile(aFile)
     , mWholeFile(true)
     , mStoredFile(false)
     , mIsTemporary(false)
   {
     NS_ASSERTION(mFile, "must have file");
   }
 
   // Create as a file with custom name
   BlobImplFile(nsIFile* aFile, const nsAString& aName,
                const nsAString& aContentType)
-    : BlobImplBase(aName, aContentType, UINT64_MAX, INT64_MAX)
+    : BlobImplBase(aName, aContentType, UINT64_MAX, INT64_MAX,
+                   BlobDirState::eUnknownIfDir)
     , mFile(aFile)
     , mWholeFile(true)
     , mStoredFile(false)
     , mIsTemporary(false)
   {
     NS_ASSERTION(mFile, "must have file");
     if (aContentType.IsEmpty()) {
       // Lazily get the content type and size
       mContentType.SetIsVoid(true);
     }
   }
 
   // Create as a stored file
   BlobImplFile(const nsAString& aName, const nsAString& aContentType,
                uint64_t aLength, nsIFile* aFile,
                indexedDB::FileInfo* aFileInfo)
-    : BlobImplBase(aName, aContentType, aLength, UINT64_MAX)
+    : BlobImplBase(aName, aContentType, aLength, UINT64_MAX,
+                   BlobDirState::eUnknownIfDir)
     , mFile(aFile)
     , mWholeFile(true)
     , mStoredFile(true)
     , mIsTemporary(false)
   {
     NS_ASSERTION(mFile, "must have file");
     mFileInfos.AppendElement(aFileInfo);
   }
@@ -801,17 +879,18 @@ public:
     , mIsTemporary(false)
   {
     NS_ASSERTION(mFile, "must have file");
     mFileInfos.AppendElement(aFileInfo);
   }
 
   // Create as a file to be later initialized
   BlobImplFile()
-    : BlobImplBase(EmptyString(), EmptyString(), UINT64_MAX, INT64_MAX)
+    : BlobImplBase(EmptyString(), EmptyString(), UINT64_MAX, INT64_MAX,
+                   BlobDirState::eUnknownIfDir)
     , mWholeFile(true)
     , mStoredFile(false)
     , mIsTemporary(false)
   {
     // Lazily get the content type and size
     mContentType.SetIsVoid(true);
     mName.SetIsVoid(true);
   }
@@ -823,16 +902,18 @@ public:
   virtual void SetLastModified(int64_t aLastModified) override;
   virtual void GetMozFullPathInternal(nsAString& aFullPath,
                                       ErrorResult& aRv) const override;
   virtual void GetInternalStream(nsIInputStream** aInputStream,
                                  ErrorResult& aRv) override;
 
   void SetPath(const nsAString& aFullPath);
 
+  virtual void LookupAndCacheIsDirectory() override;
+
 protected:
   virtual ~BlobImplFile() {
     if (mFile && mIsTemporary) {
       NS_WARNING("In temporary ~BlobImplFile");
       // Ignore errors if any, not much we can do. Clean-up will be done by
       // https://mxr.mozilla.org/mozilla-central/source/xpcom/io/nsAnonymousTemporaryFile.cpp?rev=6c1c7e45c902#127
 #ifdef DEBUG
       nsresult rv =
--- a/dom/indexedDB/FileSnapshot.cpp
+++ b/dom/indexedDB/FileSnapshot.cpp
@@ -4,16 +4,17 @@
  * 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 "FileSnapshot.h"
 
 #include "IDBFileHandle.h"
 #include "MainThreadUtils.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/dom/File.h"
 #include "mozilla/dom/MetadataHelper.h"
 
 #ifdef DEBUG
 #include "nsXULAppAPI.h"
 #endif
 
 namespace mozilla {
 namespace dom {
@@ -24,17 +25,18 @@ BlobImplSnapshot::BlobImplSnapshot(const
                                    const nsAString& aContentType,
                                    MetadataParameters* aMetadataParams,
                                    nsIFile* aFile,
                                    IDBFileHandle* aFileHandle,
                                    FileInfo* aFileInfo)
   : BlobImplBase(aName,
                  aContentType,
                  aMetadataParams->Size(),
-                 aMetadataParams->LastModified())
+                 aMetadataParams->LastModified(),
+                 BlobDirState::eUnknownIfDir)
   , mFile(aFile)
   , mWholeFile(true)
 {
   AssertSanity();
   MOZ_ASSERT(aMetadataParams);
   MOZ_ASSERT(aMetadataParams->Size() != UINT64_MAX);
   MOZ_ASSERT(aMetadataParams->LastModified() != INT64_MAX);
   MOZ_ASSERT(aFile);
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -434,17 +434,18 @@ ResolveMysteryFile(BlobImpl* aImpl,
                    const nsString& aName,
                    const nsString& aContentType,
                    uint64_t aSize,
                    uint64_t aLastModifiedDate)
 {
   BlobChild* actor = ActorFromRemoteBlobImpl(aImpl);
   if (actor) {
     return actor->SetMysteryBlobInfo(aName, aContentType,
-                                     aSize, aLastModifiedDate);
+                                     aSize, aLastModifiedDate,
+                                     BlobDirState::eUnknownIfDir);
   }
   return true;
 }
 
 bool
 ResolveMysteryBlob(BlobImpl* aImpl,
                    const nsString& aContentType,
                    uint64_t aSize)
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -665,17 +665,18 @@ public:
     : BlobImplBase(aContentType, 0)
   {
     mImmutable = true;
   }
 
   EmptyBlobImpl(const nsAString& aName,
                 const nsAString& aContentType,
                 int64_t aLastModifiedDate)
-    : BlobImplBase(aName, aContentType, 0, aLastModifiedDate)
+    : BlobImplBase(aName, aContentType, 0, aLastModifiedDate,
+                   BlobDirState::eIsNotDir)
   {
     mImmutable = true;
   }
 
 private:
   virtual already_AddRefed<BlobImpl>
   CreateSlice(uint64_t /* aStart */,
               uint64_t aLength,
@@ -728,17 +729,18 @@ public:
     mImmutable = true;
   }
 
   SameProcessInputStreamBlobImpl(const nsAString& aName,
                                  const nsAString& aContentType,
                                  uint64_t aLength,
                                  int64_t aLastModifiedDate,
                                  nsIInputStream* aInputStream)
-    : BlobImplBase(aName, aContentType, aLength, aLastModifiedDate)
+    : BlobImplBase(aName, aContentType, aLength, aLastModifiedDate,
+                   BlobDirState::eIsNotDir)
     , mInputStream(aInputStream)
   {
     MOZ_ASSERT(aLength != UINT64_MAX);
     MOZ_ASSERT(aLastModifiedDate != INT64_MAX);
     MOZ_ASSERT(aInputStream);
 
     mImmutable = true;
   }
@@ -767,22 +769,24 @@ private:
 };
 
 struct MOZ_STACK_CLASS CreateBlobImplMetadata final
 {
   nsString mContentType;
   nsString mName;
   uint64_t mLength;
   int64_t mLastModifiedDate;
+  BlobDirState mDirState;
   bool mHasRecursed;
   const bool mIsSameProcessActor;
 
   explicit CreateBlobImplMetadata(bool aIsSameProcessActor)
     : mLength(0)
     , mLastModifiedDate(0)
+    , mDirState(BlobDirState::eUnknownIfDir)
     , mHasRecursed(false)
     , mIsSameProcessActor(aIsSameProcessActor)
   {
     MOZ_COUNT_CTOR(CreateBlobImplMetadata);
 
     mName.SetIsVoid(true);
   }
 
@@ -1049,16 +1053,17 @@ CreateBlobImpl(const ParentBlobConstruct
       ASSERT_UNLESS_FUZZING();
       return nullptr;
     }
 
     metadata.mContentType = params.contentType();
     metadata.mName = params.name();
     metadata.mLength = params.length();
     metadata.mLastModifiedDate = params.modDate();
+    metadata.mDirState = BlobDirState(params.dirState());
   }
 
   nsRefPtr<BlobImpl> blobImpl =
     CreateBlobImplFromBlobData(aBlobData, metadata);
   return blobImpl.forget();
 }
 
 void
@@ -1850,30 +1855,32 @@ protected:
   const bool mIsSlice;
 
 public:
   // For File.
   RemoteBlobImpl(BlobChild* aActor,
                  const nsAString& aName,
                  const nsAString& aContentType,
                  uint64_t aLength,
-                 int64_t aModDate);
+                 int64_t aModDate,
+                 BlobDirState aDirState);
 
   // For Blob.
   RemoteBlobImpl(BlobChild* aActor,
                  const nsAString& aContentType,
                  uint64_t aLength);
 
   // For same-process blobs.
   RemoteBlobImpl(BlobChild* aActor,
                  BlobImpl* aSameProcessBlobImpl,
                  const nsAString& aName,
                  const nsAString& aContentType,
                  uint64_t aLength,
-                 int64_t aModDate);
+                 int64_t aModDate,
+                 BlobDirState aDirState);
 
   // For same-process blobs.
   RemoteBlobImpl(BlobChild* aActor,
                  BlobImpl* aSameProcessBlobImpl,
                  const nsAString& aContentType,
                  uint64_t aLength);
 
   // For mystery blobs.
@@ -2124,30 +2131,40 @@ public:
 
   virtual nsresult
   SetMutable(bool aMutable) override;
 
   virtual void
   SetLazyData(const nsAString& aName,
               const nsAString& aContentType,
               uint64_t aLength,
-              int64_t aLastModifiedDate) override;
+              int64_t aLastModifiedDate,
+              BlobDirState aDirState) override;
 
   virtual bool
   IsMemoryFile() const override;
 
   virtual bool
   IsSizeUnknown() const override;
 
   virtual bool
   IsDateUnknown() const override;
 
   virtual bool
   IsFile() const override;
 
+  virtual void
+  LookupAndCacheIsDirectory() override;
+
+  virtual bool
+  IsDirectory() const override;
+
+  virtual BlobDirState
+  GetDirState() const override;
+
   virtual bool
   MayBeClonedToOtherThreads() const override;
 
   virtual BlobChild*
   GetBlobChild() override;
 
   virtual BlobParent*
   GetBlobParent() override;
@@ -2167,18 +2184,19 @@ private:
  * BlobChild::RemoteBlobImpl
  ******************************************************************************/
 
 BlobChild::
 RemoteBlobImpl::RemoteBlobImpl(BlobChild* aActor,
                                const nsAString& aName,
                                const nsAString& aContentType,
                                uint64_t aLength,
-                               int64_t aModDate)
-  : BlobImplBase(aName, aContentType, aLength, aModDate)
+                               int64_t aModDate,
+                               BlobDirState aDirState)
+  : BlobImplBase(aName, aContentType, aLength, aModDate, aDirState)
   , mIsSlice(false)
 {
   CommonInit(aActor);
 }
 
 BlobChild::
 RemoteBlobImpl::RemoteBlobImpl(BlobChild* aActor,
                                const nsAString& aContentType,
@@ -2190,18 +2208,19 @@ RemoteBlobImpl::RemoteBlobImpl(BlobChild
 }
 
 BlobChild::
 RemoteBlobImpl::RemoteBlobImpl(BlobChild* aActor,
                                BlobImpl* aSameProcessBlobImpl,
                                const nsAString& aName,
                                const nsAString& aContentType,
                                uint64_t aLength,
-                               int64_t aModDate)
-  : BlobImplBase(aName, aContentType, aLength, aModDate)
+                               int64_t aModDate,
+                               BlobDirState aDirState)
+  : BlobImplBase(aName, aContentType, aLength, aModDate, aDirState)
   , mSameProcessBlobImpl(aSameProcessBlobImpl)
   , mIsSlice(false)
 {
   MOZ_ASSERT(aSameProcessBlobImpl);
   MOZ_ASSERT(gProcessType == GeckoProcessType_Default);
 
   CommonInit(aActor);
 }
@@ -2218,17 +2237,18 @@ RemoteBlobImpl::RemoteBlobImpl(BlobChild
   MOZ_ASSERT(aSameProcessBlobImpl);
   MOZ_ASSERT(gProcessType == GeckoProcessType_Default);
 
   CommonInit(aActor);
 }
 
 BlobChild::
 RemoteBlobImpl::RemoteBlobImpl(BlobChild* aActor)
-  : BlobImplBase(EmptyString(), EmptyString(), UINT64_MAX, INT64_MAX)
+  : BlobImplBase(EmptyString(), EmptyString(), UINT64_MAX, INT64_MAX,
+                 BlobDirState::eUnknownIfDir)
   , mIsSlice(false)
 {
   CommonInit(aActor);
 }
 
 BlobChild::
 RemoteBlobImpl::RemoteBlobImpl(const nsAString& aContentType, uint64_t aLength)
   : BlobImplBase(aContentType, aLength)
@@ -2879,17 +2899,18 @@ RemoteBlobImpl::SetMutable(bool aMutable
   return mBlobImpl->SetMutable(aMutable);
 }
 
 void
 BlobParent::
 RemoteBlobImpl::SetLazyData(const nsAString& aName,
                             const nsAString& aContentType,
                             uint64_t aLength,
-                            int64_t aLastModifiedDate)
+                            int64_t aLastModifiedDate,
+                            BlobDirState aDirState)
 {
   MOZ_CRASH("This should never be called!");
 }
 
 bool
 BlobParent::
 RemoteBlobImpl::IsMemoryFile() const
 {
@@ -2912,16 +2933,37 @@ RemoteBlobImpl::IsDateUnknown() const
 
 bool
 BlobParent::
 RemoteBlobImpl::IsFile() const
 {
   return mBlobImpl->IsFile();
 }
 
+void
+BlobParent::
+RemoteBlobImpl::LookupAndCacheIsDirectory()
+{
+  return mBlobImpl->LookupAndCacheIsDirectory();
+}
+
+bool
+BlobParent::
+RemoteBlobImpl::IsDirectory() const
+{
+  return mBlobImpl->IsDirectory();
+}
+
+BlobDirState
+BlobParent::
+RemoteBlobImpl::GetDirState() const
+{
+  return mBlobImpl->GetDirState();
+}
+
 bool
 BlobParent::
 RemoteBlobImpl::MayBeClonedToOtherThreads() const
 {
   return mBlobImpl->MayBeClonedToOtherThreads();
 }
 
 BlobChild*
@@ -3102,17 +3144,18 @@ BlobChild::CommonInit(BlobChild* aOther,
   nsRefPtr<RemoteBlobImpl> remoteBlob;
   if (otherImpl->IsFile()) {
     nsString name;
     otherImpl->GetName(name);
 
     int64_t modDate = otherImpl->GetLastModified(rv);
     MOZ_ASSERT(!rv.Failed());
 
-    remoteBlob = new RemoteBlobImpl(this, name, contentType, length, modDate);
+    remoteBlob = new RemoteBlobImpl(this, name, contentType, length, modDate,
+                                    otherImpl->GetDirState());
   } else {
     remoteBlob = new RemoteBlobImpl(this, contentType, length);
   }
 
   CommonInit(aOther->ParentID(), remoteBlob);
 }
 
 void
@@ -3144,17 +3187,18 @@ BlobChild::CommonInit(const ChildBlobCon
 
     case AnyBlobConstructorParams::TFileBlobConstructorParams: {
       const FileBlobConstructorParams& params =
         blobParams.get_FileBlobConstructorParams();
       remoteBlob = new RemoteBlobImpl(this,
                                       params.name(),
                                       params.contentType(),
                                       params.length(),
-                                      params.modDate());
+                                      params.modDate(),
+                                      BlobDirState(params.dirState()));
       break;
     }
 
     case AnyBlobConstructorParams::TSameProcessBlobConstructorParams: {
       MOZ_ASSERT(gProcessType == GeckoProcessType_Default);
 
       const SameProcessBlobConstructorParams& params =
         blobParams.get_SameProcessBlobConstructorParams();
@@ -3178,17 +3222,18 @@ BlobChild::CommonInit(const ChildBlobCon
         MOZ_ASSERT(!rv.Failed());
 
         remoteBlob =
           new RemoteBlobImpl(this,
                              blobImpl,
                              name,
                              contentType,
                              size,
-                             lastModifiedDate);
+                             lastModifiedDate,
+                             blobImpl->GetDirState());
       } else {
         remoteBlob = new RemoteBlobImpl(this, blobImpl, contentType, size);
       }
 
       break;
     }
 
     case AnyBlobConstructorParams::TMysteryBlobConstructorParams: {
@@ -3363,17 +3408,18 @@ BlobChild::GetOrCreateFromImpl(ChildMana
     if (aBlobImpl->IsFile()) {
       nsString name;
       aBlobImpl->GetName(name);
 
       int64_t modDate = aBlobImpl->GetLastModified(rv);
       MOZ_ASSERT(!rv.Failed());
 
       blobParams =
-        FileBlobConstructorParams(name, contentType, length, modDate, blobData);
+        FileBlobConstructorParams(name, contentType, length, modDate,
+                                  aBlobImpl->GetDirState(), blobData);
     } else {
       blobParams = NormalBlobConstructorParams(contentType, length, blobData);
     }
   }
 
   BlobChild* actor = new BlobChild(aManager, aBlobImpl);
 
   ParentBlobConstructorParams params(blobParams);
@@ -3536,44 +3582,48 @@ BlobChild::GetBlobImpl()
 
   return blobImpl.forget();
 }
 
 bool
 BlobChild::SetMysteryBlobInfo(const nsString& aName,
                               const nsString& aContentType,
                               uint64_t aLength,
-                              int64_t aLastModifiedDate)
+                              int64_t aLastModifiedDate,
+                              BlobDirState aDirState)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mBlobImpl);
   MOZ_ASSERT(mRemoteBlobImpl);
   MOZ_ASSERT(aLastModifiedDate != INT64_MAX);
 
-  mBlobImpl->SetLazyData(aName, aContentType, aLength, aLastModifiedDate);
+  mBlobImpl->SetLazyData(aName, aContentType, aLength, aLastModifiedDate,
+                         aDirState);
 
   FileBlobConstructorParams params(aName,
                                    aContentType,
                                    aLength,
                                    aLastModifiedDate,
+                                   aDirState,
                                    void_t() /* optionalBlobData */);
   return SendResolveMystery(params);
 }
 
 bool
 BlobChild::SetMysteryBlobInfo(const nsString& aContentType, uint64_t aLength)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mBlobImpl);
   MOZ_ASSERT(mRemoteBlobImpl);
 
   nsString voidString;
   voidString.SetIsVoid(true);
 
-  mBlobImpl->SetLazyData(voidString, aContentType, aLength, INT64_MAX);
+  mBlobImpl->SetLazyData(voidString, aContentType, aLength, INT64_MAX,
+                         BlobDirState::eUnknownIfDir);
 
   NormalBlobConstructorParams params(aContentType,
                                      aLength,
                                      void_t() /* optionalBlobData */);
   return SendResolveMystery(params);
 }
 
 void
@@ -3905,17 +3955,18 @@ BlobParent::GetOrCreateFromImpl(ParentMa
       if (aBlobImpl->IsFile()) {
         nsString name;
         aBlobImpl->GetName(name);
 
         int64_t modDate = aBlobImpl->GetLastModified(rv);
         MOZ_ASSERT(!rv.Failed());
 
         blobParams =
-          FileBlobConstructorParams(name, contentType, length, modDate, void_t());
+          FileBlobConstructorParams(name, contentType, length, modDate,
+                                    aBlobImpl->GetDirState(), void_t());
       } else {
         blobParams = NormalBlobConstructorParams(contentType, length, void_t());
       }
     }
   }
 
   nsID id;
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(gUUIDGenerator->GenerateUUIDInPlace(&id)));
@@ -4383,17 +4434,18 @@ BlobParent::RecvResolveMystery(const Res
       }
 
       nsString voidString;
       voidString.SetIsVoid(true);
 
       mBlobImpl->SetLazyData(voidString,
                              params.contentType(),
                              params.length(),
-                             INT64_MAX);
+                             INT64_MAX,
+                             BlobDirState::eUnknownIfDir);
       return true;
     }
 
     case ResolveMysteryParams::TFileBlobConstructorParams: {
       const FileBlobConstructorParams& params =
         aParams.get_FileBlobConstructorParams();
       if (NS_WARN_IF(params.name().IsVoid())) {
         ASSERT_UNLESS_FUZZING();
@@ -4408,17 +4460,18 @@ BlobParent::RecvResolveMystery(const Res
       if (NS_WARN_IF(params.modDate() == INT64_MAX)) {
         ASSERT_UNLESS_FUZZING();
         return false;
       }
 
       mBlobImpl->SetLazyData(params.name(),
                              params.contentType(),
                              params.length(),
-                             params.modDate());
+                             params.modDate(),
+                             BlobDirState(params.dirState()));
       return true;
     }
 
     default:
       MOZ_CRASH("Unknown params!");
   }
 
   MOZ_CRASH("Should never get here!");
--- a/dom/ipc/BlobChild.h
+++ b/dom/ipc/BlobChild.h
@@ -20,21 +20,24 @@ namespace mozilla {
 namespace ipc {
 
 class PBackgroundChild;
 
 } // namespace ipc
 
 namespace dom {
 
+class Blob;
 class BlobImpl;
 class ContentChild;
 class nsIContentChild;
 class PBlobStreamChild;
 
+enum BlobDirState : uint32_t;
+
 class BlobChild final
   : public PBlobChild
 {
   typedef mozilla::ipc::PBackgroundChild PBackgroundChild;
 
   class RemoteBlobImpl;
   friend class RemoteBlobImpl;
 
@@ -109,17 +112,18 @@ public:
   already_AddRefed<BlobImpl>
   GetBlobImpl();
 
   // Use this for files.
   bool
   SetMysteryBlobInfo(const nsString& aName,
                      const nsString& aContentType,
                      uint64_t aLength,
-                     int64_t aLastModifiedDate);
+                     int64_t aLastModifiedDate,
+                     BlobDirState aDirState);
 
   // Use this for non-file blobs.
   bool
   SetMysteryBlobInfo(const nsString& aContentType, uint64_t aLength);
 
   void
   AssertIsOnOwningThread() const
 #ifdef DEBUG
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -62,16 +62,17 @@ struct NormalBlobConstructorParams
 };
 
 struct FileBlobConstructorParams
 {
   nsString name;
   nsString contentType;
   uint64_t length;
   int64_t modDate;
+  uint32_t dirState;
 
   // This must be of type BlobData in a child->parent message, and will always
   // be of type void_t in a parent->child message.
   OptionalBlobData optionalBlobData;
 };
 
 struct SlicedBlobConstructorParams
 {