Bug 1186932 - Implement support for form submission of a picked directory - part 1 - FormData and Directory, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Thu, 14 Jul 2016 09:01:31 +0200
changeset 330067 3ac9e026e72eff099c8deba43370b95907140492
parent 330066 8f6e45ae207fcf9365d4af9a78b22edaff6e3985
child 330068 8105df56b23209b8cb3389dfe483214472e0bb3f
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1186932
milestone50.0a1
Bug 1186932 - Implement support for form submission of a picked directory - part 1 - FormData and Directory, r=smaug
dom/base/FormData.cpp
dom/base/FormData.h
dom/base/StructuredCloneHolder.cpp
dom/base/StructuredCloneHolder.h
dom/html/HTMLFormSubmission.cpp
dom/html/HTMLFormSubmission.h
dom/html/HTMLInputElement.cpp
dom/webidl/FormData.webidl
--- a/dom/base/FormData.cpp
+++ b/dom/base/FormData.cpp
@@ -124,48 +124,54 @@ FormData::Append(const nsAString& aName,
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   AddNameBlobOrNullPair(aName, file);
 }
 
 void
+FormData::Append(const nsAString& aName, Directory* aDirectory)
+{
+  AddNameDirectoryPair(aName, aDirectory);
+}
+
+void
 FormData::Delete(const nsAString& aName)
 {
   // We have to use this slightly awkward for loop since uint32_t >= 0 is an
   // error for being always true.
   for (uint32_t i = mFormData.Length(); i-- > 0; ) {
     if (aName.Equals(mFormData[i].name)) {
       mFormData.RemoveElementAt(i);
     }
   }
 }
 
 void
 FormData::Get(const nsAString& aName,
-              Nullable<OwningBlobOrUSVString>& aOutValue)
+              Nullable<OwningBlobOrDirectoryOrUSVString>& aOutValue)
 {
   for (uint32_t i = 0; i < mFormData.Length(); ++i) {
     if (aName.Equals(mFormData[i].name)) {
       aOutValue.SetValue() = mFormData[i].value;
       return;
     }
   }
 
   aOutValue.SetNull();
 }
 
 void
 FormData::GetAll(const nsAString& aName,
-                 nsTArray<OwningBlobOrUSVString>& aValues)
+                 nsTArray<OwningBlobOrDirectoryOrUSVString>& aValues)
 {
   for (uint32_t i = 0; i < mFormData.Length(); ++i) {
     if (aName.Equals(mFormData[i].name)) {
-      OwningBlobOrUSVString* element = aValues.AppendElement();
+      OwningBlobOrDirectoryOrUSVString* element = aValues.AppendElement();
       *element = mFormData[i].value;
     }
   }
 }
 
 bool
 FormData::Has(const nsAString& aName)
 {
@@ -195,16 +201,26 @@ FormData::AddNameBlobOrNullPair(const ns
     return rv.StealNSResult();
   }
 
   FormDataTuple* data = mFormData.AppendElement();
   SetNameFilePair(data, aName, file);
   return NS_OK;
 }
 
+nsresult
+FormData::AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory)
+{
+  MOZ_ASSERT(aDirectory);
+
+  FormDataTuple* data = mFormData.AppendElement();
+  SetNameDirectoryPair(data, aName, aDirectory);
+  return NS_OK;
+}
+
 FormData::FormDataTuple*
 FormData::RemoveAllOthersAndGetFirstFormDataTuple(const nsAString& aName)
 {
   FormDataTuple* lastFoundTuple = nullptr;
   uint32_t lastFoundIndex = mFormData.Length();
   // We have to use this slightly awkward for loop since uint32_t >= 0 is an
   // error for being always true.
   for (uint32_t i = mFormData.Length(); i-- > 0; ) {
@@ -260,17 +276,17 @@ FormData::GetIterableLength() const
 
 const nsAString&
 FormData::GetKeyAtIndex(uint32_t aIndex) const
 {
   MOZ_ASSERT(aIndex < mFormData.Length());
   return mFormData[aIndex].name;
 }
 
-const OwningBlobOrUSVString&
+const OwningBlobOrDirectoryOrUSVString&
 FormData::GetValueAtIndex(uint32_t aIndex) const
 {
   MOZ_ASSERT(aIndex < mFormData.Length());
   return mFormData[aIndex].value;
 }
 
 void
 FormData::SetNameValuePair(FormDataTuple* aData,
@@ -292,16 +308,29 @@ FormData::SetNameFilePair(FormDataTuple*
   MOZ_ASSERT(aData);
   MOZ_ASSERT(aFile);
 
   aData->name = aName;
   aData->wasNullBlob = false;
   aData->value.SetAsBlob() = aFile;
 }
 
+void
+FormData::SetNameDirectoryPair(FormDataTuple* aData,
+                               const nsAString& aName,
+                               Directory* aDirectory)
+{
+  MOZ_ASSERT(aData);
+  MOZ_ASSERT(aDirectory);
+
+  aData->name = aName;
+  aData->wasNullBlob = false;
+  aData->value.SetAsDirectory() = aDirectory;
+}
+
 // -------------------------------------------------------------------------
 // nsIDOMFormData
 
 NS_IMETHODIMP
 FormData::Append(const nsAString& aName, nsIVariant* aValue)
 {
   uint16_t dataType;
   nsresult rv = aValue->GetDataType(&dataType);
@@ -376,20 +405,23 @@ FormData::GetSendInfo(nsIInputStream** a
 
   for (uint32_t i = 0; i < mFormData.Length(); ++i) {
     if (mFormData[i].wasNullBlob) {
       MOZ_ASSERT(mFormData[i].value.IsUSVString());
       fs.AddNameBlobOrNullPair(mFormData[i].name, nullptr);
     } else if (mFormData[i].value.IsUSVString()) {
       fs.AddNameValuePair(mFormData[i].name,
                           mFormData[i].value.GetAsUSVString());
-    } else {
-      MOZ_ASSERT(mFormData[i].value.IsBlob());
+    } else if (mFormData[i].value.IsBlob()) {
       fs.AddNameBlobOrNullPair(mFormData[i].name,
                                mFormData[i].value.GetAsBlob());
+    } else {
+      MOZ_ASSERT(mFormData[i].value.IsDirectory());
+      fs.AddNameDirectoryPair(mFormData[i].name,
+                              mFormData[i].value.GetAsDirectory());
     }
   }
 
   fs.GetContentType(aContentType);
   aCharset.Truncate();
   *aContentLength = 0;
   NS_ADDREF(*aBody = fs.GetSubmissionBody(aContentLength));
 
--- a/dom/base/FormData.h
+++ b/dom/base/FormData.h
@@ -31,33 +31,37 @@ class FormData final : public nsIDOMForm
 {
 private:
   ~FormData() {}
 
   struct FormDataTuple
   {
     nsString name;
     bool wasNullBlob;
-    OwningBlobOrUSVString value;
+    OwningBlobOrDirectoryOrUSVString value;
   };
 
   // Returns the FormDataTuple to modify. This may be null, in which case
   // no element with aName was found.
   FormDataTuple*
   RemoveAllOthersAndGetFirstFormDataTuple(const nsAString& aName);
 
   void SetNameValuePair(FormDataTuple* aData,
                         const nsAString& aName,
                         const nsAString& aValue,
                         bool aWasNullBlob = false);
 
   void SetNameFilePair(FormDataTuple* aData,
                        const nsAString& aName,
                        File* aFile);
 
+  void SetNameDirectoryPair(FormDataTuple* aData,
+                            const nsAString& aName,
+                            Directory* aDirectory);
+
 public:
   explicit FormData(nsISupports* aOwner = nullptr);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(FormData,
                                                          nsIDOMFormData)
 
   NS_DECL_NSIDOMFORMDATA
@@ -75,57 +79,63 @@ public:
 
   static already_AddRefed<FormData>
   Constructor(const GlobalObject& aGlobal,
               const Optional<NonNull<HTMLFormElement> >& aFormElement,
               ErrorResult& aRv);
 
   void Append(const nsAString& aName, const nsAString& aValue,
               ErrorResult& aRv);
+
   void Append(const nsAString& aName, Blob& aBlob,
               const Optional<nsAString>& aFilename,
               ErrorResult& aRv);
 
+  void Append(const nsAString& aName, Directory* aDirectory);
+
   void Delete(const nsAString& aName);
 
-  void Get(const nsAString& aName, Nullable<OwningBlobOrUSVString>& aOutValue);
+  void Get(const nsAString& aName, Nullable<OwningBlobOrDirectoryOrUSVString>& aOutValue);
 
-  void GetAll(const nsAString& aName, nsTArray<OwningBlobOrUSVString>& aValues);
+  void GetAll(const nsAString& aName, nsTArray<OwningBlobOrDirectoryOrUSVString>& aValues);
 
   bool Has(const nsAString& aName);
 
   void Set(const nsAString& aName, Blob& aBlob,
            const Optional<nsAString>& aFilename,
            ErrorResult& aRv);
   void Set(const nsAString& aName, const nsAString& aValue,
            ErrorResult& aRv);
 
   uint32_t GetIterableLength() const;
 
   const nsAString& GetKeyAtIndex(uint32_t aIndex) const;
 
-  const OwningBlobOrUSVString& GetValueAtIndex(uint32_t aIndex) const;
+  const OwningBlobOrDirectoryOrUSVString& GetValueAtIndex(uint32_t aIndex) const;
 
   // HTMLFormSubmission
   virtual nsresult
   GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
 
   virtual nsresult AddNameValuePair(const nsAString& aName,
                                     const nsAString& aValue) override
   {
     FormDataTuple* data = mFormData.AppendElement();
     SetNameValuePair(data, aName, aValue);
     return NS_OK;
   }
 
   virtual nsresult AddNameBlobOrNullPair(const nsAString& aName,
                                          Blob* aBlob) override;
 
+  virtual nsresult AddNameDirectoryPair(const nsAString& aName,
+                                        Directory* aDirectory) override;
+
   typedef bool (*FormDataEntryCallback)(const nsString& aName,
-                                        const OwningBlobOrUSVString& aValue,
+                                        const OwningBlobOrDirectoryOrUSVString& aValue,
                                         void* aClosure);
 
   uint32_t
   Length() const
   {
     return mFormData.Length();
   }
 
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -738,23 +738,21 @@ WriteDirectory(JSStructuredCloneWriter* 
   nsAutoString path;
   aDirectory->GetFullRealPath(path);
 
   size_t charSize = sizeof(nsString::char_type);
   return JS_WriteUint32Pair(aWriter, SCTAG_DOM_DIRECTORY, path.Length()) &&
          JS_WriteBytes(aWriter, path.get(), path.Length() * charSize);
 }
 
-JSObject*
-ReadDirectory(JSContext* aCx,
-              JSStructuredCloneReader* aReader,
-              uint32_t aPathLength,
-              StructuredCloneHolder* aHolder)
+already_AddRefed<Directory>
+ReadDirectoryInternal(JSStructuredCloneReader* aReader,
+                      uint32_t aPathLength,
+                      StructuredCloneHolder* aHolder)
 {
-  MOZ_ASSERT(aCx);
   MOZ_ASSERT(aReader);
   MOZ_ASSERT(aHolder);
 
   nsAutoString path;
   path.SetLength(aPathLength);
   size_t charSize = sizeof(nsString::char_type);
   if (!JS_ReadBytes(aReader, (void*) path.BeginWriting(),
                     aPathLength * charSize)) {
@@ -763,25 +761,43 @@ ReadDirectory(JSContext* aCx,
 
   nsCOMPtr<nsIFile> file;
   nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true,
                                       getter_AddRefs(file));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return nullptr;
   }
 
+  RefPtr<Directory> directory =
+    Directory::Create(aHolder->ParentDuringRead(), file);
+  return directory.forget();
+}
+
+JSObject*
+ReadDirectory(JSContext* aCx,
+              JSStructuredCloneReader* aReader,
+              uint32_t aPathLength,
+              StructuredCloneHolder* aHolder)
+{
+  MOZ_ASSERT(aCx);
+  MOZ_ASSERT(aReader);
+  MOZ_ASSERT(aHolder);
+
   // RefPtr<Directory> needs to go out of scope before toObject() is
   // called because the static analysis thinks dereferencing XPCOM objects
   // can GC (because in some cases it can!), and a return statement with a
   // JSObject* type means that JSObject* is on the stack as a raw pointer
   // while destructors are running.
   JS::Rooted<JS::Value> val(aCx);
   {
     RefPtr<Directory> directory =
-      Directory::Create(aHolder->ParentDuringRead(), file);
+      ReadDirectoryInternal(aReader, aPathLength, aHolder);
+    if (!directory) {
+      return nullptr;
+    }
 
     if (!ToJSValue(aCx, directory, &val)) {
       return nullptr;
     }
   }
 
   return &val.toObject();
 }
@@ -921,16 +937,25 @@ ReadFormData(JSContext* aCx,
 
         ErrorResult rv;
         formData->Append(name, *blob, thirdArg, rv);
         if (NS_WARN_IF(rv.Failed())) {
           rv.SuppressException();
           return nullptr;
         }
 
+      } else if (tag == SCTAG_DOM_DIRECTORY) {
+        RefPtr<Directory> directory =
+          ReadDirectoryInternal(aReader, indexOrLengthOfString, aHolder);
+        if (!directory) {
+          return nullptr;
+        }
+
+        formData->Append(name, directory);
+
       } else {
         MOZ_ASSERT(tag == 0);
 
         nsAutoString value;
         value.SetLength(indexOrLengthOfString);
         size_t charSize = sizeof(nsString::char_type);
         if (!JS_ReadBytes(aReader, (void*) value.BeginWriting(),
                           indexOrLengthOfString * charSize)) {
@@ -956,16 +981,19 @@ ReadFormData(JSContext* aCx,
 
 // The format of the FormData serialization is:
 // - pair of ints: SCTAG_DOM_FORMDATA, Length of the FormData elements
 // - for each Element element:
 //   - name string
 //   - if it's a blob:
 //     - pair of ints: SCTAG_DOM_BLOB, index of the BlobImpl in the array
 //       mBlobImplArray.
+//   - if it's a directory (See WriteDirectory):
+//     - pair of ints: SCTAG_DOM_DIRECTORY, path length
+//     - path as string
 //   - else:
 //     - pair of ints: 0, string length
 //     - value string
 bool
 WriteFormData(JSStructuredCloneWriter* aWriter,
               FormData* aFormData,
               StructuredCloneHolder* aHolder)
 {
@@ -986,17 +1014,17 @@ WriteFormData(JSStructuredCloneWriter* a
   public:
     Closure(JSStructuredCloneWriter* aWriter,
             StructuredCloneHolder* aHolder)
       : mWriter(aWriter),
         mHolder(aHolder)
     { }
 
     static bool
-    Write(const nsString& aName, const OwningBlobOrUSVString& aValue,
+    Write(const nsString& aName, const OwningBlobOrDirectoryOrUSVString& aValue,
           void* aClosure)
     {
       Closure* closure = static_cast<Closure*>(aClosure);
       if (!WriteString(closure->mWriter, aName)) {
         return false;
       }
 
       if (aValue.IsBlob()) {
@@ -1005,16 +1033,28 @@ WriteFormData(JSStructuredCloneWriter* a
                                 closure->mHolder->BlobImpls().Length())) {
           return false;
         }
 
         closure->mHolder->BlobImpls().AppendElement(blobImpl);
         return true;
       }
 
+      if (aValue.IsDirectory()) {
+        Directory* directory = aValue.GetAsDirectory();
+
+        if (closure->mHolder->SupportedContext() !=
+              StructuredCloneHolder::SameProcessSameThread &&
+            !directory->ClonableToDifferentThreadOrProcess()) {
+          return false;
+        }
+
+        return WriteDirectory(closure->mWriter, directory);
+      }
+
       size_t charSize = sizeof(nsString::char_type);
       if (!JS_WriteUint32Pair(closure->mWriter, 0,
                               aValue.GetAsUSVString().Length()) ||
           !JS_WriteBytes(closure->mWriter, aValue.GetAsUSVString().get(),
                          aValue.GetAsUSVString().Length() * charSize)) {
         return false;
       }
 
--- a/dom/base/StructuredCloneHolder.h
+++ b/dom/base/StructuredCloneHolder.h
@@ -189,16 +189,21 @@ public:
   }
 
   nsTArray<RefPtr<BlobImpl>>& BlobImpls()
   {
     MOZ_ASSERT(mSupportsCloning, "Blobs cannot be taken/set if cloning is not supported.");
     return mBlobImplArray;
   }
 
+  ContextSupport SupportedContext() const
+  {
+    return mSupportedContext;
+  }
+
   // The parent object is set internally just during the Read(). This method
   // can be used by read functions to retrieve it.
   nsISupports* ParentDuringRead() const
   {
     return mParent;
   }
 
   // This must be called if the transferring has ports generated by Read().
--- a/dom/html/HTMLFormSubmission.cpp
+++ b/dom/html/HTMLFormSubmission.cpp
@@ -93,16 +93,19 @@ public:
 
   virtual nsresult
   AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
 
   virtual nsresult
   AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
 
   virtual nsresult
+  AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
+
+  virtual nsresult
   GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
 
   virtual bool SupportsIsindexSubmission() override
   {
     return true;
   }
 
   virtual nsresult AddIsindex(const nsAString& aValue) override;
@@ -189,16 +192,24 @@ FSURLEncoded::AddNameBlobOrNullPair(cons
     mWarnedFileControl = true;
   }
 
   nsAutoString filename;
   RetrieveFileName(aBlob, filename);
   return AddNameValuePair(aName, filename);
 }
 
+nsresult
+FSURLEncoded::AddNameDirectoryPair(const nsAString& aName,
+                                   Directory* aDirectory)
+{
+  // TODO
+  return NS_OK;
+}
+
 void
 HandleMailtoSubject(nsCString& aPath)
 {
   // Walk through the string and see if we have a subject already.
   bool hasSubject = false;
   bool hasParams = false;
   int32_t paramSep = aPath.FindChar('?');
   while (paramSep != kNotFound && paramSep < (int32_t)aPath.Length()) {
@@ -559,16 +570,24 @@ FSMultipartFormData::AddNameBlobOrNullPa
 
   // CRLF after file
   mPostDataChunk.AppendLiteral(CRLF);
 
   return NS_OK;
 }
 
 nsresult
+FSMultipartFormData::AddNameDirectoryPair(const nsAString& aName,
+                                          Directory* aDirectory)
+{
+  // TODO
+  return NS_OK;
+}
+
+nsresult
 FSMultipartFormData::GetEncodedSubmission(nsIURI* aURI,
                                           nsIInputStream** aPostDataStream)
 {
   nsresult rv;
 
   // Make header
   nsCOMPtr<nsIMIMEInputStream> mimeStream
     = do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv);
@@ -619,16 +638,19 @@ public:
 
   virtual nsresult
   AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
 
   virtual nsresult
   AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
 
   virtual nsresult
+  AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
+
+  virtual nsresult
   GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
 
 private:
   nsString mBody;
 };
 
 nsresult
 FSTextPlain::AddNameValuePair(const nsAString& aName, const nsAString& aValue)
@@ -647,16 +669,24 @@ FSTextPlain::AddNameBlobOrNullPair(const
 {
   nsAutoString filename;
   RetrieveFileName(aBlob, filename);
   AddNameValuePair(aName, filename);
   return NS_OK;
 }
 
 nsresult
+FSTextPlain::AddNameDirectoryPair(const nsAString& aName,
+                                  Directory* aDirectory)
+{
+  // TODO
+  return NS_OK;
+}
+
+nsresult
 FSTextPlain::GetEncodedSubmission(nsIURI* aURI,
                                   nsIInputStream** aPostDataStream)
 {
   nsresult rv = NS_OK;
 
   // XXX HACK We are using the standard URL mechanism to give the body to the
   // mailer instead of passing the post data stream to it, since that sounds
   // hard.
--- a/dom/html/HTMLFormSubmission.h
+++ b/dom/html/HTMLFormSubmission.h
@@ -17,16 +17,17 @@ class nsIURI;
 class nsIInputStream;
 class nsGenericHTMLElement;
 class nsIMultiplexInputStream;
 
 namespace mozilla {
 namespace dom {
 
 class Blob;
+class Directory;
 
 /**
  * Class for form submissions; encompasses the function to call to submit as
  * well as the form submission name/value pairs
  */
 class HTMLFormSubmission
 {
 public:
@@ -64,16 +65,25 @@ public:
    * @param aBlob the blob to submit. The file's name will be used if the Blob
    * is actually a File, otherwise 'blob' string is used instead if the aBlob is
    * not null.
    */
   virtual nsresult
   AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) = 0;
 
   /**
+   * Submit a name/directory pair
+   *
+   * @param aName the name of the parameter
+   * @param aBlob the directory to submit.
+   */
+  virtual nsresult AddNameDirectoryPair(const nsAString& aName,
+                                        Directory* aDirectory) = 0;
+
+  /**
    * Reports whether the instance supports AddIsindex().
    *
    * @return true if supported.
    */
   virtual bool SupportsIsindexSubmission()
   {
     return false;
   }
@@ -175,16 +185,19 @@ public:
  
   virtual nsresult
   AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
 
   virtual nsresult
   AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
 
   virtual nsresult
+  AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
+
+  virtual nsresult
   GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
 
   void GetContentType(nsACString& aContentType)
   {
     aContentType =
       NS_LITERAL_CSTRING("multipart/form-data; boundary=") + mBoundary;
   }
 
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -6583,26 +6583,28 @@ HTMLInputElement::SubmitNamesValues(HTML
   // Submit file if its input type=file and this encoding method accepts files
   //
   if (mType == NS_FORM_INPUT_FILE) {
     // Submit files
 
     const nsTArray<OwningFileOrDirectory>& files =
       GetFilesOrDirectoriesInternal();
 
-    bool hasBlobs = false;
+    if (files.IsEmpty()) {
+      aFormSubmission->AddNameBlobOrNullPair(name, nullptr);
+      return NS_OK;
+    }
+
     for (uint32_t i = 0; i < files.Length(); ++i) {
       if (files[i].IsFile()) {
-        hasBlobs = true;
         aFormSubmission->AddNameBlobOrNullPair(name, files[i].GetAsFile());
-      }
-    }
-
-    if (!hasBlobs) {
-      aFormSubmission->AddNameBlobOrNullPair(name, nullptr);
+      } else {
+        MOZ_ASSERT(files[i].IsDirectory());
+        aFormSubmission->AddNameDirectoryPair(name, files[i].GetAsDirectory());
+      }
     }
 
     return NS_OK;
   }
 
   if (mType == NS_FORM_INPUT_HIDDEN && name.EqualsLiteral("_charset_")) {
     nsCString charset;
     aFormSubmission->GetCharset(charset);
--- a/dom/webidl/FormData.webidl
+++ b/dom/webidl/FormData.webidl
@@ -2,17 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * The origin of this IDL file is
  * http://xhr.spec.whatwg.org
  */
 
-typedef (Blob or USVString) FormDataEntryValue;
+typedef (Blob or Directory or USVString) FormDataEntryValue;
 
 [Constructor(optional HTMLFormElement form),
  Exposed=(Window,Worker)]
 interface FormData {
   [Throws]
   void append(USVString name, Blob value, optional USVString filename);
   [Throws]
   void append(USVString name, USVString value);