Bug 1289254 - Support dnd for webkitdirectory, r=baku
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Fri, 29 Jul 2016 14:41:38 +0300
changeset 349384 f1f06933167fde6fe0f9a606819667e6e6285315
parent 349383 ebdaaac3f55d32e161abdb687b5024c6f1495960
child 349385 5e254e995379e6cfda29c286bb40440e2ee705b8
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1289254
milestone50.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 1289254 - Support dnd for webkitdirectory, r=baku
dom/base/File.cpp
dom/base/File.h
dom/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
dom/ipc/Blob.cpp
dom/ipc/DOMTypes.ipdlh
dom/ipc/PBlob.ipdl
dom/webidl/HTMLInputElement.webidl
layout/forms/nsFileControlFrame.cpp
layout/forms/nsFileControlFrame.h
--- a/dom/base/File.cpp
+++ b/dom/base/File.cpp
@@ -986,16 +986,26 @@ BlobImplFile::GetInternalStream(nsIInput
     aRv = NS_NewLocalFileInputStream(aStream, mFile, -1, -1, sFileStreamFlags);
     return;
   }
 
   aRv = NS_NewPartialLocalFileInputStream(aStream, mFile, mStart, mLength,
                                           -1, -1, sFileStreamFlags);
 }
 
+bool
+BlobImplFile::IsDirectory() const
+{
+  bool isDirectory = false;
+  if (mFile) {
+    mFile->IsDirectory(&isDirectory);
+  }
+  return isDirectory;
+}
+
 ////////////////////////////////////////////////////////////////////////////
 // EmptyBlobImpl implementation
 
 NS_IMPL_ISUPPORTS_INHERITED0(EmptyBlobImpl, BlobImpl)
 
 already_AddRefed<BlobImpl>
 EmptyBlobImpl::CreateSlice(uint64_t aStart, uint64_t aLength,
                            const nsAString& aContentType,
--- a/dom/base/File.h
+++ b/dom/base/File.h
@@ -324,16 +324,23 @@ public:
   virtual bool IsMemoryFile() const = 0;
 
   virtual bool IsSizeUnknown() const = 0;
 
   virtual bool IsDateUnknown() const = 0;
 
   virtual bool IsFile() const = 0;
 
+  // Returns true if the BlobImpl is backed by an nsIFile and the underlying
+  // file is a directory.
+  virtual bool IsDirectory() const
+  {
+    return false;
+  }
+
   // True if this implementation can be sent to other threads.
   virtual bool MayBeClonedToOtherThreads() const
   {
     return true;
   }
 
 protected:
   virtual ~BlobImpl() {}
@@ -707,16 +714,18 @@ public:
   virtual void GetType(nsAString& aType) override;
   virtual int64_t GetLastModified(ErrorResult& aRv) override;
   virtual void SetLastModified(int64_t aLastModified) override;
   virtual void GetMozFullPathInternal(nsAString& aFullPath,
                                       ErrorResult& aRv) const override;
   virtual void GetInternalStream(nsIInputStream** aInputStream,
                                  ErrorResult& aRv) override;
 
+  virtual bool IsDirectory() const override;
+
   // We always have size and date for this kind of blob.
   virtual bool IsSizeUnknown() const override { return false; }
   virtual bool IsDateUnknown() const override { return false; }
 
 protected:
   virtual ~BlobImplFile() {
     if (mFile && mIsTemporary) {
       NS_WARNING("In temporary ~BlobImplFile");
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -2834,52 +2834,62 @@ HTMLInputElement::GetDisplayFileName(nsA
 }
 
 void
 HTMLInputElement::SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
                                         bool aSetValueChanged)
 {
   ClearGetFilesHelpers();
 
+  if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
+    HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
+    mEntries.Clear();
+  }
+
   mFilesOrDirectories.Clear();
   mFilesOrDirectories.AppendElements(aFilesOrDirectories);
 
   AfterSetFilesOrDirectories(aSetValueChanged);
 }
 
 void
 HTMLInputElement::SetFiles(nsIDOMFileList* aFiles,
                            bool aSetValueChanged)
 {
   RefPtr<FileList> files = static_cast<FileList*>(aFiles);
   mFilesOrDirectories.Clear();
   ClearGetFilesHelpers();
 
+  if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
+    HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
+    mEntries.Clear();
+  }
+
   if (aFiles) {
     uint32_t listLength;
     aFiles->GetLength(&listLength);
     for (uint32_t i = 0; i < listLength; i++) {
       OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
       element->SetAsFile() = files->Item(i);
     }
   }
 
   AfterSetFilesOrDirectories(aSetValueChanged);
 }
 
 // This method is used for testing only.
 void
 HTMLInputElement::MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
 {
+  SetFilesOrDirectories(aFilesOrDirectories, true);
+
   if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
     UpdateEntries(aFilesOrDirectories);
   }
 
-  SetFilesOrDirectories(aFilesOrDirectories, true);
-
   RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
     new DispatchChangeEventCallback(this);
 
   if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
       HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
     ErrorResult rv;
     GetFilesHelper* helper = GetOrCreateGetFilesHelper(true /* recursionFlag */,
                                                        rv);
@@ -8095,17 +8105,17 @@ HTMLInputElement::GetOrCreateGetFilesHel
   }
 
   return mGetFilesNonRecursiveHelper;
 }
 
 void
 HTMLInputElement::UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
 {
-  mEntries.Clear();
+  MOZ_ASSERT(mEntries.IsEmpty());
 
   nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
   MOZ_ASSERT(global);
 
   RefPtr<DOMFileSystem> fs = DOMFileSystem::Create(global);
   if (NS_WARN_IF(!fs)) {
     return;
   }
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -801,16 +801,18 @@ public:
    *
    *   http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#floating-point-numbers
    *
    * then this function will return the number parsed as a Decimal, otherwise
    * it will return a Decimal for which Decimal::isFinite() will return false.
    */
   static Decimal StringToDecimal(const nsAString& aValue);
 
+  void UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories);
+
 protected:
   virtual ~HTMLInputElement();
 
   virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   // Pull IsSingleLineTextControl into our scope, otherwise it'd be hidden
   // by the nsITextControlElement version.
   using nsGenericHTMLFormElementWithState::IsSingleLineTextControl;
@@ -961,18 +963,16 @@ protected:
    */
   nsresult MaybeSubmitForm(nsPresContext* aPresContext);
 
   /**
    * Update mFileList with the currently selected file.
    */
   void UpdateFileList();
 
-  void UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories);
-
   /**
    * Called after calling one of the SetFilesOrDirectories() functions.
    * This method can explore the directory recursively if needed.
    */
   void AfterSetFilesOrDirectories(bool aSetValueChanged);
 
   /**
    * Recursively explore the directory and populate mFileOrDirectories correctly
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -1708,25 +1708,35 @@ protected:
   // SendCreatedFromKnownBlob() is received. This is used only with KnownBlob
   // params in the CTOR of a IPC BlobImpl.
   RefPtr<BlobImpl> mDifferentProcessBlobImpl;
 
   RefPtr<BlobImpl> mSameProcessBlobImpl;
 
   const bool mIsSlice;
 
+  const bool mIsDirectory;
+
 public:
+
+  enum BlobImplIsDirectory
+  {
+    eNotDirectory,
+    eDirectory
+  };
+
   // For File.
   RemoteBlobImpl(BlobChild* aActor,
                  BlobImpl* aRemoteBlobImpl,
                  const nsAString& aName,
                  const nsAString& aContentType,
                  const nsAString& aPath,
                  uint64_t aLength,
                  int64_t aModDate,
+                 BlobImplIsDirectory aIsDirectory,
                  bool aIsSameProcessBlob);
 
   // For Blob.
   RemoteBlobImpl(BlobChild* aActor,
                  BlobImpl* aRemoteBlobImpl,
                  const nsAString& aContentType,
                  uint64_t aLength,
                  bool aIsSameProcessBlob);
@@ -1770,16 +1780,19 @@ public:
   RemoteBlobImpl*
   BaseRemoteBlobImpl() const;
 
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual void
   GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const override;
 
+  virtual bool
+  IsDirectory() const override;
+
   virtual already_AddRefed<BlobImpl>
   CreateSlice(uint64_t aStart,
               uint64_t aLength,
               const nsAString& aContentType,
               ErrorResult& aRv) override;
 
   virtual void
   GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv) override;
@@ -1943,16 +1956,19 @@ public:
   SetLastModified(int64_t aLastModified) override;
 
   virtual void
   GetMozFullPath(nsAString& aName, ErrorResult& aRv) const override;
 
   virtual void
   GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const override;
 
+  virtual bool
+  IsDirectory() const override;
+
   virtual uint64_t
   GetSize(ErrorResult& aRv) override;
 
   virtual void
   GetType(nsAString& aType) override;
 
   virtual uint64_t
   GetSerialNumber() const override;
@@ -2029,19 +2045,20 @@ private:
 BlobChild::
 RemoteBlobImpl::RemoteBlobImpl(BlobChild* aActor,
                                BlobImpl* aRemoteBlobImpl,
                                const nsAString& aName,
                                const nsAString& aContentType,
                                const nsAString& aPath,
                                uint64_t aLength,
                                int64_t aModDate,
+                               BlobImplIsDirectory aIsDirectory,
                                bool aIsSameProcessBlob)
   : BlobImplBase(aName, aContentType, aLength, aModDate)
-  , mIsSlice(false)
+  , mIsSlice(false), mIsDirectory(aIsDirectory == eDirectory)
 {
   SetPath(aPath);
 
   if (aIsSameProcessBlob) {
     MOZ_ASSERT(aRemoteBlobImpl);
     mSameProcessBlobImpl = aRemoteBlobImpl;
     MOZ_ASSERT(gProcessType == GeckoProcessType_Default);
   } else {
@@ -2053,42 +2070,43 @@ RemoteBlobImpl::RemoteBlobImpl(BlobChild
 
 BlobChild::
 RemoteBlobImpl::RemoteBlobImpl(BlobChild* aActor,
                                BlobImpl* aRemoteBlobImpl,
                                const nsAString& aContentType,
                                uint64_t aLength,
                                bool aIsSameProcessBlob)
   : BlobImplBase(aContentType, aLength)
-  , mIsSlice(false)
+  , mIsSlice(false), mIsDirectory(false)
 {
   if (aIsSameProcessBlob) {
     MOZ_ASSERT(aRemoteBlobImpl);
     mSameProcessBlobImpl = aRemoteBlobImpl;
     MOZ_ASSERT(gProcessType == GeckoProcessType_Default);
   } else {
     mDifferentProcessBlobImpl = aRemoteBlobImpl;
   }
 
   CommonInit(aActor);
 }
 
 BlobChild::
 RemoteBlobImpl::RemoteBlobImpl(BlobChild* aActor)
   : BlobImplBase(EmptyString(), EmptyString(), UINT64_MAX, INT64_MAX)
-  , mIsSlice(false)
+  , mIsSlice(false), mIsDirectory(false)
 {
   CommonInit(aActor);
 }
 
 BlobChild::
 RemoteBlobImpl::RemoteBlobImpl(const nsAString& aContentType, uint64_t aLength)
   : BlobImplBase(aContentType, aLength)
   , mActor(nullptr)
   , mIsSlice(true)
+  , mIsDirectory(false)
 {
   mImmutable = true;
 }
 
 void
 BlobChild::
 RemoteBlobImpl::CommonInit(BlobChild* aActor)
 {
@@ -2191,16 +2209,23 @@ RemoteBlobImpl::GetMozFullPathInternal(n
   if (!mActor->SendGetFilePath(&filePath)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   aFilePath = filePath;
 }
 
+bool
+BlobChild::
+RemoteBlobImpl::IsDirectory() const
+{
+  return mIsDirectory;
+}
+
 already_AddRefed<BlobImpl>
 BlobChild::
 RemoteBlobImpl::CreateSlice(uint64_t aStart,
                             uint64_t aLength,
                             const nsAString& aContentType,
                             ErrorResult& aRv)
 {
   // May be called on any thread.
@@ -2638,16 +2663,23 @@ RemoteBlobImpl::GetMozFullPath(nsAString
 
 void
 BlobParent::
 RemoteBlobImpl::GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const
 {
   mBlobImpl->GetMozFullPathInternal(aFileName, aRv);
 }
 
+bool
+BlobParent::
+RemoteBlobImpl::IsDirectory() const
+{
+  return mBlobImpl->IsDirectory();
+}
+
 uint64_t
 BlobParent::
 RemoteBlobImpl::GetSize(ErrorResult& aRv)
 {
   return mBlobImpl->GetSize(aRv);
 }
 
 void
@@ -2948,19 +2980,24 @@ BlobChild::CommonInit(BlobChild* aOther,
     otherImpl->GetName(name);
 
     nsAutoString path;
     otherImpl->GetPath(path);
 
     int64_t modDate = otherImpl->GetLastModified(rv);
     MOZ_ASSERT(!rv.Failed());
 
+    RemoteBlobImpl::BlobImplIsDirectory directory = otherImpl->IsDirectory() ?
+      RemoteBlobImpl::BlobImplIsDirectory::eDirectory :
+      RemoteBlobImpl::BlobImplIsDirectory::eNotDirectory;
+
     remoteBlob =
       new RemoteBlobImpl(this, otherImpl, name, contentType, path,
-                         length, modDate, false /* SameProcessBlobImpl */);
+                         length, modDate, directory,
+                         false /* SameProcessBlobImpl */);
   } else {
     remoteBlob = new RemoteBlobImpl(this, otherImpl, contentType, length,
                                     false /* SameProcessBlobImpl */);
   }
 
   // This RemoteBlob must be kept alive untill RecvCreatedFromKnownBlob is
   // called because the parent will send this notification and we must be able
   // to manage it.
@@ -2996,23 +3033,27 @@ BlobChild::CommonInit(const ChildBlobCon
         new RemoteBlobImpl(this, nullptr, params.contentType(), params.length(),
                            false /* SameProcessBlobImpl */);
       break;
     }
 
     case AnyBlobConstructorParams::TFileBlobConstructorParams: {
       const FileBlobConstructorParams& params =
         blobParams.get_FileBlobConstructorParams();
+      RemoteBlobImpl::BlobImplIsDirectory directory = params.isDirectory() ?
+        RemoteBlobImpl::BlobImplIsDirectory::eDirectory :
+        RemoteBlobImpl::BlobImplIsDirectory::eNotDirectory;
       remoteBlob = new RemoteBlobImpl(this,
                                       nullptr,
                                       params.name(),
                                       params.contentType(),
                                       params.path(),
                                       params.length(),
                                       params.modDate(),
+                                      directory,
                                       false /* SameProcessBlobImpl */);
       break;
     }
 
     case AnyBlobConstructorParams::TSameProcessBlobConstructorParams: {
       MOZ_ASSERT(gProcessType == GeckoProcessType_Default);
 
       const SameProcessBlobConstructorParams& params =
@@ -3034,24 +3075,30 @@ BlobChild::CommonInit(const ChildBlobCon
         blobImpl->GetName(name);
 
         nsAutoString path;
         blobImpl->GetPath(path);
 
         int64_t lastModifiedDate = blobImpl->GetLastModified(rv);
         MOZ_ASSERT(!rv.Failed());
 
+        RemoteBlobImpl::BlobImplIsDirectory directory =
+          blobImpl->IsDirectory() ?
+            RemoteBlobImpl::BlobImplIsDirectory::eDirectory :
+            RemoteBlobImpl::BlobImplIsDirectory::eNotDirectory;
+
         remoteBlob =
           new RemoteBlobImpl(this,
                              blobImpl,
                              name,
                              contentType,
                              path,
                              size,
                              lastModifiedDate,
+                             directory,
                              true /* SameProcessBlobImpl */);
       } else {
         remoteBlob = new RemoteBlobImpl(this, blobImpl, contentType, size,
                                         true /* SameProcessBlobImpl */);
       }
 
       break;
     }
@@ -3226,17 +3273,17 @@ BlobChild::GetOrCreateFromImpl(ChildMana
       nsAutoString path;
       aBlobImpl->GetPath(path);
 
       int64_t modDate = aBlobImpl->GetLastModified(rv);
       MOZ_ASSERT(!rv.Failed());
 
       blobParams =
         FileBlobConstructorParams(name, contentType, path, length, modDate,
-                                  blobData);
+                                  aBlobImpl->IsDirectory(), blobData);
     } else {
       blobParams = NormalBlobConstructorParams(contentType, length, blobData);
     }
   }
 
   BlobChild* actor = new BlobChild(aManager, aBlobImpl);
 
   ParentBlobConstructorParams params(blobParams);
@@ -3403,26 +3450,29 @@ BlobChild::GetBlobImpl()
 bool
 BlobChild::SetMysteryBlobInfo(const nsString& aName,
                               const nsString& aContentType,
                               uint64_t aLength,
                               int64_t aLastModifiedDate)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mBlobImpl);
+  MOZ_ASSERT(!mBlobImpl->IsDirectory());
   MOZ_ASSERT(mRemoteBlobImpl);
+  MOZ_ASSERT(!mRemoteBlobImpl->IsDirectory());
   MOZ_ASSERT(aLastModifiedDate != INT64_MAX);
 
   mBlobImpl->SetLazyData(aName, aContentType, aLength, aLastModifiedDate);
 
   FileBlobConstructorParams params(aName,
                                    aContentType,
                                    EmptyString(),
                                    aLength,
                                    aLastModifiedDate,
+                                   mBlobImpl->IsDirectory(),
                                    void_t() /* optionalBlobData */);
   return SendResolveMystery(params);
 }
 
 bool
 BlobChild::SetMysteryBlobInfo(const nsString& aContentType, uint64_t aLength)
 {
   AssertIsOnOwningThread();
@@ -3778,17 +3828,17 @@ BlobParent::GetOrCreateFromImpl(ParentMa
         nsAutoString path;
         aBlobImpl->GetPath(path);
 
         int64_t modDate = aBlobImpl->GetLastModified(rv);
         MOZ_ASSERT(!rv.Failed());
 
         blobParams =
           FileBlobConstructorParams(name, contentType, path, length, modDate,
-                                    void_t());
+                                    aBlobImpl->IsDirectory(), void_t());
       } else {
         blobParams = NormalBlobConstructorParams(contentType, length, void_t());
       }
     }
   }
 
   nsID id;
   MOZ_ALWAYS_SUCCEEDS(gUUIDGenerator->GenerateUUIDInPlace(&id));
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -67,16 +67,17 @@ struct NormalBlobConstructorParams
 
 struct FileBlobConstructorParams
 {
   nsString name;
   nsString contentType;
   nsString path;
   uint64_t length;
   int64_t modDate;
+  bool isDirectory;
 
   // 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
 {
--- a/dom/ipc/PBlob.ipdl
+++ b/dom/ipc/PBlob.ipdl
@@ -38,17 +38,16 @@ parent:
     returns (InputStreamParams params, OptionalFileDescriptorSet fds);
 
   sync WaitForSliceCreation();
 
   // Use only for testing!
   sync GetFileId()
     returns (int64_t fileId);
 
-  // Use only for testing!
   sync GetFilePath()
     returns (nsString filePath);
 
 child:
   // This method must be called by the parent when the PBlobParent is fully
   // created in order to release the known blob.
   async CreatedFromKnownBlob();
 };
--- a/dom/webidl/HTMLInputElement.webidl
+++ b/dom/webidl/HTMLInputElement.webidl
@@ -221,14 +221,14 @@ interface MozPhonetic {
   readonly attribute DOMString phonetic;
 };
 
 HTMLInputElement implements MozImageLoadingContent;
 HTMLInputElement implements MozPhonetic;
 
 // Webkit/Blink
 partial interface HTMLInputElement {
-  [Pref="dom.webkitBlink.filesystem.enabled", Cached, Constant]
+  [Pref="dom.webkitBlink.filesystem.enabled", Frozen, Cached, Pure]
   readonly attribute sequence<Entry> webkitEntries;
 
   [Pref="dom.webkitBlink.dirPicker.enabled", BinaryName="WebkitDirectoryAttr", SetterThrows]
           attribute boolean webkitdirectory;
 };
--- a/layout/forms/nsFileControlFrame.cpp
+++ b/layout/forms/nsFileControlFrame.cpp
@@ -14,16 +14,18 @@
 #include "mozilla/dom/HTMLButtonElement.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/Preferences.h"
 #include "nsNodeInfoManager.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsContentUtils.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/dom/DOMStringList.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/FileList.h"
 #include "nsIDOMDragEvent.h"
 #include "nsIDOMFileList.h"
 #include "nsContentList.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsTextNode.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
@@ -189,16 +191,52 @@ NS_QUERYFRAME_HEAD(nsFileControlFrame)
   NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
 
 void 
 nsFileControlFrame::SetFocus(bool aOn, bool aRepaint)
 {
 }
 
+static void
+AppendBlobImplAsDirectory(nsTArray<OwningFileOrDirectory>& aArray,
+                          BlobImpl* aBlobImpl,
+                          nsIContent* aContent)
+{
+  MOZ_ASSERT(aBlobImpl);
+  MOZ_ASSERT(aBlobImpl->IsDirectory());
+
+  nsAutoString fullpath;
+  ErrorResult err;
+  aBlobImpl->GetMozFullPath(fullpath, err);
+  if (err.Failed()) {
+    err.SuppressException();
+    return;
+  }
+
+  nsCOMPtr<nsIFile> file;
+  NS_ConvertUTF16toUTF8 path(fullpath);
+  nsresult rv = NS_NewNativeLocalFile(path, true, getter_AddRefs(file));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  nsPIDOMWindowInner* inner = aContent->OwnerDoc()->GetInnerWindow();
+  if (!inner || !inner->IsCurrentInnerWindow()) {
+    return;
+  }
+
+  RefPtr<Directory> directory =
+    Directory::Create(inner, file);
+  MOZ_ASSERT(directory);
+
+  OwningFileOrDirectory* element = aArray.AppendElement();
+  element->SetAsDirectory() = directory;
+}
+
 /**
  * This is called when we receive a drop or a dragover.
  */
 NS_IMETHODIMP
 nsFileControlFrame::DnDListener::HandleEvent(nsIDOMEvent* aEvent)
 {
   NS_ASSERTION(mFrame, "We should have been unregistered");
 
@@ -215,17 +253,17 @@ nsFileControlFrame::DnDListener::HandleE
 
   nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
   dragEvent->GetDataTransfer(getter_AddRefs(dataTransfer));
   if (!IsValidDropData(dataTransfer)) {
     return NS_OK;
   }
 
 
-  nsIContent* content = mFrame->GetContent();
+  nsCOMPtr<nsIContent> content = mFrame->GetContent();
   bool supportsMultiple = content && content->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple);
   if (!CanDropTheseFiles(dataTransfer, supportsMultiple)) {
     dataTransfer->SetDropEffect(NS_LITERAL_STRING("none"));
     aEvent->StopPropagation();
     return NS_OK;
   }
 
   nsAutoString eventType;
@@ -243,52 +281,137 @@ nsFileControlFrame::DnDListener::HandleE
     NS_ASSERTION(content, "The frame has no content???");
 
     HTMLInputElement* inputElement = HTMLInputElement::FromContent(content);
     NS_ASSERTION(inputElement, "No input element for this file upload control frame!");
 
     nsCOMPtr<nsIDOMFileList> fileList;
     dataTransfer->GetFiles(getter_AddRefs(fileList));
 
-    inputElement->SetFiles(fileList, true);
-    nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
-                                         NS_LITERAL_STRING("change"), true,
-                                         false);
+    RefPtr<BlobImpl> webkitDir;
+    nsresult rv =
+      GetBlobImplForWebkitDirectory(fileList, getter_AddRefs(webkitDir));
+    NS_ENSURE_SUCCESS(rv, NS_OK);
+
+    nsTArray<OwningFileOrDirectory> array;
+    if (webkitDir) {
+      AppendBlobImplAsDirectory(array, webkitDir, content);
+      inputElement->MozSetDndFilesAndDirectories(array);
+    } else {
+      bool blinkFileSystemEnabled =
+        Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false);
+      if (blinkFileSystemEnabled) {
+        FileList* files = static_cast<FileList*>(fileList.get());
+        if (files) {
+          for (uint32_t i = 0; i < files->Length(); ++i) {
+            File* file = files->Item(i);
+            if (file) {
+              if (file->Impl() && file->Impl()->IsDirectory()) {
+                AppendBlobImplAsDirectory(array, file->Impl(), content);
+              } else {
+                OwningFileOrDirectory* element = array.AppendElement();
+                element->SetAsFile() = file;
+              }
+            }
+          }
+        }
+      }
+
+      // This is rather ugly. Pass the directories as Files using SetFiles,
+      // but then if blink filesystem API is enabled, it wants
+      // FileOrDirectory array.
+      inputElement->SetFiles(fileList, true);
+      if (blinkFileSystemEnabled) {
+        inputElement->UpdateEntries(array);
+      }
+      nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
+                                           NS_LITERAL_STRING("input"), true,
+                                           false);
+      nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
+                                           NS_LITERAL_STRING("change"), true,
+                                           false);
+    }
   }
 
   return NS_OK;
 }
 
-/* static */ bool
+nsresult
+nsFileControlFrame::DnDListener::GetBlobImplForWebkitDirectory(nsIDOMFileList* aFileList,
+                                                               BlobImpl** aBlobImpl)
+{
+  *aBlobImpl = nullptr;
+
+  HTMLInputElement* inputElement =
+    HTMLInputElement::FromContent(mFrame->GetContent());
+  bool webkitDirPicker =
+    Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+    inputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory);
+  if (!webkitDirPicker) {
+    return NS_OK;
+  }
+
+  if (!aFileList) {
+    return NS_ERROR_FAILURE;
+  }
+
+  FileList* files = static_cast<FileList*>(aFileList);
+  // webkitdirectory doesn't care about the length of the file list but
+  // only about the first item on it.
+  uint32_t len = files->Length();
+  if (len) {
+    File* file = files->Item(0);
+    if (file) {
+      BlobImpl* impl = file->Impl();
+      if (impl && impl->IsDirectory()) {
+        RefPtr<BlobImpl> retVal = impl;
+        retVal.swap(*aBlobImpl);
+        return NS_OK;
+      }
+    }
+  }
+
+  return NS_ERROR_FAILURE;
+}
+
+bool
 nsFileControlFrame::DnDListener::IsValidDropData(nsIDOMDataTransfer* aDOMDataTransfer)
 {
   nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(aDOMDataTransfer);
   NS_ENSURE_TRUE(dataTransfer, false);
 
   // We only support dropping files onto a file upload control
   ErrorResult rv;
   RefPtr<DOMStringList> types = dataTransfer->GetTypes(rv);
   if (NS_WARN_IF(rv.Failed())) {
     rv.SuppressException();
     return false;
   }
 
   return types->Contains(NS_LITERAL_STRING("Files"));
 }
 
-/* static */ bool
+bool
 nsFileControlFrame::DnDListener::CanDropTheseFiles(nsIDOMDataTransfer* aDOMDataTransfer,
                                                    bool aSupportsMultiple)
 {
   nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(aDOMDataTransfer);
   NS_ENSURE_TRUE(dataTransfer, false);
 
   nsCOMPtr<nsIDOMFileList> fileList;
   dataTransfer->GetFiles(getter_AddRefs(fileList));
 
+  RefPtr<BlobImpl> webkitDir;
+  nsresult rv =
+    GetBlobImplForWebkitDirectory(fileList, getter_AddRefs(webkitDir));
+  // Just check if either there isn't webkitdirectory attribute, or
+  // fileList has a directory which can be dropped to the element.
+  // No need to use webkitDir for anything here.
+  NS_ENSURE_SUCCESS(rv, false);
+
   uint32_t listLength = 0;
   if (fileList) {
     fileList->GetLength(&listLength);
   }
   return listLength <= 1 || aSupportsMultiple;
 }
 
 nscoord
--- a/layout/forms/nsFileControlFrame.h
+++ b/layout/forms/nsFileControlFrame.h
@@ -9,16 +9,22 @@
 #include "mozilla/Attributes.h"
 #include "nsBlockFrame.h"
 #include "nsIFormControlFrame.h"
 #include "nsIDOMEventListener.h"
 #include "nsIAnonymousContentCreator.h"
 #include "nsCOMPtr.h"
 
 class nsIDOMDataTransfer;
+class nsIDOMFileList;
+namespace mozilla {
+namespace dom {
+class BlobImpl;
+} // namespace dom
+} // namespace mozilla
 
 class nsFileControlFrame : public nsBlockFrame,
                            public nsIFormControlFrame,
                            public nsIAnonymousContentCreator
 {
 public:
   explicit nsFileControlFrame(nsStyleContext* aContext);
 
@@ -111,18 +117,21 @@ protected:
   class DnDListener: public MouseListener {
   public:
     explicit DnDListener(nsFileControlFrame* aFrame)
       : MouseListener(aFrame)
     {}
 
     NS_DECL_NSIDOMEVENTLISTENER
 
-    static bool IsValidDropData(nsIDOMDataTransfer* aDOMDataTransfer);
-    static bool CanDropTheseFiles(nsIDOMDataTransfer* aDOMDataTransfer, bool aSupportsMultiple);
+    nsresult GetBlobImplForWebkitDirectory(nsIDOMFileList* aFileList,
+                                           mozilla::dom::BlobImpl** aBlobImpl);
+
+    bool IsValidDropData(nsIDOMDataTransfer* aDOMDataTransfer);
+    bool CanDropTheseFiles(nsIDOMDataTransfer* aDOMDataTransfer, bool aSupportsMultiple);
   };
 
   virtual bool IsFrameOfType(uint32_t aFlags) const override
   {
     return nsBlockFrame::IsFrameOfType(aFlags &
       ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
   }