Bug 1162658 - Update FormData to match latest spec, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 20 Jan 2016 17:25:03 +0000
changeset 317793 1079f6d91f65f24c401133e6c313c7f405749c82
parent 317792 8b78eccf2c296d54da1d8ed02d1293a8ff6138e3
child 317794 0f7510fa983c1804792cb4a43d341b6b4e533172
push id1079
push userjlund@mozilla.com
push dateFri, 15 Apr 2016 21:02:33 +0000
treeherdermozilla-release@575fbf6786d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1162658
milestone46.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 1162658 - Update FormData to match latest spec, r=smaug
dom/base/File.cpp
dom/base/File.h
dom/base/FormData.cpp
dom/base/FormData.h
dom/base/StructuredCloneHolder.cpp
dom/base/test/test_bug1187157.html
dom/html/HTMLInputElement.cpp
dom/html/nsFormSubmission.cpp
dom/html/nsFormSubmission.h
dom/html/test/formData_test.js
dom/html/test/test_formSubmission.html
dom/webidl/FormData.webidl
--- a/dom/base/File.cpp
+++ b/dom/base/File.cpp
@@ -953,33 +953,33 @@ BlobImplFile::LookupAndCacheIsDirectory(
              "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;
 }
 
 ////////////////////////////////////////////////////////////////////////////
-// BlobImplEmptyFile implementation
+// EmptyBlobImpl implementation
 
-NS_IMPL_ISUPPORTS_INHERITED0(BlobImplEmptyFile, BlobImpl)
+NS_IMPL_ISUPPORTS_INHERITED0(EmptyBlobImpl, BlobImpl)
 
 already_AddRefed<BlobImpl>
-BlobImplEmptyFile::CreateSlice(uint64_t aStart, uint64_t aLength,
-                               const nsAString& aContentType,
-                               ErrorResult& aRv)
+EmptyBlobImpl::CreateSlice(uint64_t aStart, uint64_t aLength,
+                           const nsAString& aContentType,
+                           ErrorResult& aRv)
 {
   MOZ_ASSERT(!aStart && !aLength);
-  RefPtr<BlobImpl> impl = new BlobImplEmptyFile(aContentType);
+  RefPtr<BlobImpl> impl = new EmptyBlobImpl(aContentType);
   return impl.forget();
 }
 
 void
-BlobImplEmptyFile::GetInternalStream(nsIInputStream** aStream,
-                                     ErrorResult& aRv)
+EmptyBlobImpl::GetInternalStream(nsIInputStream** aStream,
+                                 ErrorResult& aRv)
 {
   nsresult rv = NS_NewCStringInputStream(aStream, EmptyCString());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(rv);
     return;
   }
 }
 
--- a/dom/base/File.h
+++ b/dom/base/File.h
@@ -65,16 +65,17 @@ class Blob : public nsIDOMBlob
 public:
   NS_DECL_NSIDOMBLOB
   NS_DECL_NSIXHRSENDABLE
   NS_DECL_NSIMUTABLE
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Blob, nsIDOMBlob)
 
+  // This creates a Blob or a File based on the type of BlobImpl.
   static Blob*
   Create(nsISupports* aParent, BlobImpl* aImpl);
 
   static already_AddRefed<Blob>
   Create(nsISupports* aParent, const nsAString& aContentType,
          uint64_t aLength);
 
   static already_AddRefed<Blob>
@@ -828,37 +829,37 @@ private:
   CreateSlice(uint64_t aStart, uint64_t aLength,
               const nsAString& aContentType, ErrorResult& aRv) override;
 
   nsCOMPtr<nsIFile> mFile;
   bool mWholeFile;
   bool mIsTemporary;
 };
 
-class BlobImplEmptyFile final : public BlobImplBase
+class EmptyBlobImpl final : public BlobImplBase
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
-  explicit BlobImplEmptyFile(const nsAString& aContentType)
-    : BlobImplBase(EmptyString(), aContentType, 0 /* aLength */)
+  explicit EmptyBlobImpl(const nsAString& aContentType)
+    : BlobImplBase(aContentType, 0 /* aLength */)
   {}
 
   virtual void GetInternalStream(nsIInputStream** aStream,
                                  ErrorResult& aRv) override;
 
   virtual already_AddRefed<BlobImpl>
   CreateSlice(uint64_t aStart, uint64_t aLength,
               const nsAString& aContentType, ErrorResult& aRv) override;
 
   virtual bool IsMemoryFile() const override
   {
     return true;
   }
 
 private:
-  ~BlobImplEmptyFile() {}
+  ~EmptyBlobImpl() {}
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_File_h
--- a/dom/base/FormData.cpp
+++ b/dom/base/FormData.cpp
@@ -18,42 +18,26 @@ using namespace mozilla::dom;
 FormData::FormData(nsISupports* aOwner)
   : nsFormSubmission(NS_LITERAL_CSTRING("UTF-8"), nullptr)
   , mOwner(aOwner)
 {
 }
 
 namespace {
 
-// Implements steps 3 and 4 of the "create an entry" algorithm of FormData.
-already_AddRefed<File>
-CreateNewFileInstance(Blob& aBlob, const Optional<nsAString>& aFilename,
-                      ErrorResult& aRv)
+already_AddRefed<Blob>
+GetBlobForFormDataStorage(Blob& aBlob, const Optional<nsAString>& aFilename,
+                          ErrorResult& aRv)
 {
-  // Step 3 "If value is a Blob object and not a File object, set value to
-  // a new File object, representing the same bytes, whose name attribute value
-  // is "blob"."
-  // Step 4 "If value is a File object and filename is given, set value to
-  // a new File object, representing the same bytes, whose name attribute
-  // value is filename."
-  nsAutoString filename;
-  if (aFilename.WasPassed()) {
-    filename = aFilename.Value();
-  } else {
-    // If value is already a File and filename is not passed, the spec says not
-    // to create a new instance.
-    RefPtr<File> file = aBlob.ToFile();
-    if (file) {
-      return file.forget();
-    }
-
-    filename = NS_LITERAL_STRING("blob");
+  if (!aFilename.WasPassed()) {
+    RefPtr<Blob> blob = &aBlob;
+    return blob.forget();
   }
 
-  RefPtr<File> file = aBlob.ToFile(filename, aRv);
+  RefPtr<File> file = aBlob.ToFile(aFilename.Value(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   return file.forget();
 }
 
 } // namespace
@@ -73,17 +57,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Fo
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FormData)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
 
   for (uint32_t i = 0, len = tmp->mFormData.Length(); i < len; ++i) {
     ImplCycleCollectionTraverse(cb, tmp->mFormData[i].value,
-                                "mFormData[i].GetAsFile()", 0);
+                                "mFormData[i].GetAsBlob()", 0);
   }
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(FormData)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(FormData)
@@ -111,59 +95,59 @@ FormData::Append(const nsAString& aName,
                  ErrorResult& aRv)
 {
   AddNameValuePair(aName, aValue);
 }
 
 void
 FormData::Append(const nsAString& aName, Blob& aBlob,
                  const Optional<nsAString>& aFilename,
-                   ErrorResult& aRv)
+                 ErrorResult& aRv)
 {
-  RefPtr<File> file = CreateNewFileInstance(aBlob, aFilename, aRv);
+  RefPtr<Blob> blob = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
-  AddNameFilePair(aName, file);
+  AddNameBlobPair(aName, blob);
 }
 
 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<OwningFileOrUSVString>& aOutValue)
+              Nullable<OwningBlobOrUSVString>& 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<OwningFileOrUSVString>& aValues)
+                 nsTArray<OwningBlobOrUSVString>& aValues)
 {
   for (uint32_t i = 0; i < mFormData.Length(); ++i) {
     if (aName.Equals(mFormData[i].name)) {
-      OwningFileOrUSVString* element = aValues.AppendElement();
+      OwningBlobOrUSVString* element = aValues.AppendElement();
       *element = mFormData[i].value;
     }
   }
 }
 
 bool
 FormData::Has(const nsAString& aName)
 {
@@ -172,22 +156,22 @@ FormData::Has(const nsAString& aName)
       return true;
     }
   }
 
   return false;
 }
 
 nsresult
-FormData::AddNameFilePair(const nsAString& aName, File* aFile)
+FormData::AddNameBlobPair(const nsAString& aName, Blob* aBlob)
 {
-  MOZ_ASSERT(aFile);
+  MOZ_ASSERT(aBlob);
 
   FormDataTuple* data = mFormData.AppendElement();
-  SetNameFilePair(data, aName, aFile);
+  SetNameBlobPair(data, aName, aBlob);
   return NS_OK;
 }
 
 FormData::FormDataTuple*
 FormData::RemoveAllOthersAndGetFirstFormDataTuple(const nsAString& aName)
 {
   FormDataTuple* lastFoundTuple = nullptr;
   uint32_t lastFoundIndex = mFormData.Length();
@@ -210,22 +194,22 @@ FormData::RemoveAllOthersAndGetFirstForm
 
 void
 FormData::Set(const nsAString& aName, Blob& aBlob,
               const Optional<nsAString>& aFilename,
               ErrorResult& aRv)
 {
   FormDataTuple* tuple = RemoveAllOthersAndGetFirstFormDataTuple(aName);
   if (tuple) {
-    RefPtr<File> file = CreateNewFileInstance(aBlob, aFilename, aRv);
+    RefPtr<Blob> blob = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
     if (NS_WARN_IF(aRv.Failed())) {
       return;
     }
 
-    SetNameFilePair(tuple, aName, file);
+    SetNameBlobPair(tuple, aName, blob);
   } else {
     Append(aName, aBlob, aFilename, aRv);
   }
 }
 
 void
 FormData::Set(const nsAString& aName, const nsAString& aValue,
               ErrorResult& aRv)
@@ -246,17 +230,17 @@ FormData::GetIterableLength() const
 
 const nsAString&
 FormData::GetKeyAtIndex(uint32_t aIndex) const
 {
   MOZ_ASSERT(aIndex < mFormData.Length());
   return mFormData[aIndex].name;
 }
 
-const OwningFileOrUSVString&
+const OwningBlobOrUSVString&
 FormData::GetValueAtIndex(uint32_t aIndex) const
 {
   MOZ_ASSERT(aIndex < mFormData.Length());
   return mFormData[aIndex].value;
 }
 
 void
 FormData::SetNameValuePair(FormDataTuple* aData,
@@ -264,25 +248,25 @@ FormData::SetNameValuePair(FormDataTuple
                            const nsAString& aValue)
 {
   MOZ_ASSERT(aData);
   aData->name = aName;
   aData->value.SetAsUSVString() = aValue;
 }
 
 void
-FormData::SetNameFilePair(FormDataTuple* aData,
+FormData::SetNameBlobPair(FormDataTuple* aData,
                           const nsAString& aName,
-                          File* aFile)
+                          Blob* aBlob)
 {
   MOZ_ASSERT(aData);
-  MOZ_ASSERT(aFile);
+  MOZ_ASSERT(aBlob);
 
   aData->name = aName;
-  aData->value.SetAsFile() = aFile;
+  aData->value.SetAsBlob() = aBlob;
 }
 
 // -------------------------------------------------------------------------
 // nsIDOMFormData
 
 NS_IMETHODIMP
 FormData::Append(const nsAString& aName, nsIVariant* aValue)
 {
@@ -353,18 +337,31 @@ FormData::Constructor(const GlobalObject
 
 NS_IMETHODIMP
 FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
                       nsACString& aContentType, nsACString& aCharset)
 {
   nsFSMultipartFormData fs(NS_LITERAL_CSTRING("UTF-8"), nullptr);
 
   for (uint32_t i = 0; i < mFormData.Length(); ++i) {
-    if (mFormData[i].value.IsFile()) {
-      fs.AddNameFilePair(mFormData[i].name, mFormData[i].value.GetAsFile());
+    if (mFormData[i].value.IsBlob()) {
+      RefPtr<File> file = mFormData[i].value.GetAsBlob()->ToFile();
+      if (file) {
+        fs.AddNameBlobPair(mFormData[i].name, file);
+        continue;
+      }
+
+      ErrorResult rv;
+      file =
+        mFormData[i].value.GetAsBlob()->ToFile(NS_LITERAL_STRING("blob"), rv);
+      if (NS_WARN_IF(rv.Failed())) {
+        return rv.StealNSResult();
+      }
+
+      fs.AddNameBlobPair(mFormData[i].name, file);
     } else if (mFormData[i].value.IsUSVString()) {
       fs.AddNameValuePair(mFormData[i].name,
                           mFormData[i].value.GetAsUSVString());
     } else {
       MOZ_CRASH("This should no be possible.");
     }
   }
 
--- a/dom/base/FormData.h
+++ b/dom/base/FormData.h
@@ -30,31 +30,31 @@ class FormData final : public nsIDOMForm
                        public nsWrapperCache
 {
 private:
   ~FormData() {}
 
   struct FormDataTuple
   {
     nsString name;
-    OwningFileOrUSVString value;
+    OwningBlobOrUSVString 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);
 
-  void SetNameFilePair(FormDataTuple* aData,
+  void SetNameBlobPair(FormDataTuple* aData,
                        const nsAString& aName,
-                       File* aFile);
+                       Blob* aBlob);
 
 public:
   explicit FormData(nsISupports* aOwner = nullptr);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(FormData,
                                                          nsIDOMFormData)
 
@@ -79,51 +79,51 @@ public:
   void Append(const nsAString& aName, const nsAString& aValue,
               ErrorResult& aRv);
   void Append(const nsAString& aName, Blob& aBlob,
               const Optional<nsAString>& aFilename,
               ErrorResult& aRv);
 
   void Delete(const nsAString& aName);
 
-  void Get(const nsAString& aName, Nullable<OwningFileOrUSVString>& aOutValue);
+  void Get(const nsAString& aName, Nullable<OwningBlobOrUSVString>& aOutValue);
 
-  void GetAll(const nsAString& aName, nsTArray<OwningFileOrUSVString>& aValues);
+  void GetAll(const nsAString& aName, nsTArray<OwningBlobOrUSVString>& 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 OwningFileOrUSVString& GetValueAtIndex(uint32_t aIndex) const;
+  const OwningBlobOrUSVString& GetValueAtIndex(uint32_t aIndex) const;
 
   // nsFormSubmission
   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 AddNameFilePair(const nsAString& aName,
-                                   File* aFile) override;
+  virtual nsresult AddNameBlobPair(const nsAString& aName,
+                                   Blob* aBlob) override;
 
   typedef bool (*FormDataEntryCallback)(const nsString& aName,
-                                        const OwningFileOrUSVString& aValue,
+                                        const OwningBlobOrUSVString& aValue,
                                         void* aClosure);
 
   uint32_t
   Length() const
   {
     return mFormData.Length();
   }
 
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -831,24 +831,23 @@ ReadFormData(JSContext* aCx,
         return nullptr;
       }
 
       if (tag == SCTAG_DOM_BLOB) {
         MOZ_ASSERT(indexOrLengthOfString < aHolder->BlobImpls().Length());
 
         RefPtr<BlobImpl> blobImpl =
           aHolder->BlobImpls()[indexOrLengthOfString];
-        MOZ_ASSERT(blobImpl->IsFile());
 
-        RefPtr<File> file =
-          File::Create(aHolder->ParentDuringRead(), blobImpl);
-        MOZ_ASSERT(file);
+        RefPtr<Blob> blob =
+          Blob::Create(aHolder->ParentDuringRead(), blobImpl);
+        MOZ_ASSERT(blob);
 
         ErrorResult rv;
-        formData->Append(name, *file, thirdArg, rv);
+        formData->Append(name, *blob, thirdArg, rv);
         if (NS_WARN_IF(rv.Failed())) {
           return nullptr;
         }
 
       } else {
         MOZ_ASSERT(tag == 0);
 
         nsAutoString value;
@@ -907,26 +906,26 @@ WriteFormData(JSStructuredCloneWriter* a
   public:
     Closure(JSStructuredCloneWriter* aWriter,
             StructuredCloneHolder* aHolder)
       : mWriter(aWriter),
         mHolder(aHolder)
     { }
 
     static bool
-    Write(const nsString& aName, const OwningFileOrUSVString& aValue,
+    Write(const nsString& aName, const OwningBlobOrUSVString& aValue,
           void* aClosure)
     {
       Closure* closure = static_cast<Closure*>(aClosure);
       if (!WriteString(closure->mWriter, aName)) {
         return false;
       }
 
-      if (aValue.IsFile()) {
-        BlobImpl* blobImpl = aValue.GetAsFile()->Impl();
+      if (aValue.IsBlob()) {
+        BlobImpl* blobImpl = aValue.GetAsBlob()->Impl();
         if (!JS_WriteUint32Pair(closure->mWriter, SCTAG_DOM_BLOB,
                                 closure->mHolder->BlobImpls().Length())) {
           return false;
         }
 
         closure->mHolder->BlobImpls().AppendElement(blobImpl);
         return true;
       }
--- a/dom/base/test/test_bug1187157.html
+++ b/dom/base/test/test_bug1187157.html
@@ -11,19 +11,18 @@ https://bugzilla.mozilla.org/show_bug.cg
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=789315">Mozilla Bug 789315</a>
 <form id="a"><input name="b" type="file"/></form>
 
 <script type="text/javascript">
   var obj = new FormData(document.getElementById('a')).get('b');
 
-  ok(obj instanceof File, "This should return an empty File");
+  ok(obj instanceof Blob, "This should return an empty Blob");
   is(obj.size, 0, "Size should be 0");
-  is(obj.name, "", "Name should be an empty string");
   is(obj.type, "application/octet-stream", "Type should be application/octet-stream");
 
   var o = obj.slice(10, 100, "foo/bar");
   is(o.size, 0, "The new blob has size 0");
   is(o.type, "foo/bar", "The new blob has type foo/bar");
 </script>
 
 </body>
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -5567,24 +5567,24 @@ HTMLInputElement::SubmitNamesValues(nsFo
   // Submit file if its input type=file and this encoding method accepts files
   //
   if (mType == NS_FORM_INPUT_FILE) {
     // Submit files
 
     const nsTArray<RefPtr<File>>& files = GetFilesInternal();
 
     for (uint32_t i = 0; i < files.Length(); ++i) {
-      aFormSubmission->AddNameFilePair(name, files[i]);
+      aFormSubmission->AddNameBlobPair(name, files[i]);
     }
 
     if (files.IsEmpty()) {
       RefPtr<BlobImpl> blobImpl =
-        new BlobImplEmptyFile(NS_LITERAL_STRING("application/octet-stream"));
-      RefPtr<File> file = File::Create(OwnerDoc()->GetInnerWindow(), blobImpl);
-      aFormSubmission->AddNameFilePair(name, file);
+        new EmptyBlobImpl(NS_LITERAL_STRING("application/octet-stream"));
+      RefPtr<Blob> blob = Blob::Create(OwnerDoc()->GetInnerWindow(), blobImpl);
+      aFormSubmission->AddNameBlobPair(name, blob);
     }
 
     return NS_OK;
   }
 
   if (mType == NS_FORM_INPUT_HIDDEN && name.EqualsLiteral("_charset_")) {
     nsCString charset;
     aFormSubmission->GetCharset(charset);
--- a/dom/html/nsFormSubmission.cpp
+++ b/dom/html/nsFormSubmission.cpp
@@ -49,16 +49,27 @@ SendJSWarning(nsIDocument* aDocument,
 {
   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                   NS_LITERAL_CSTRING("HTML"), aDocument,
                                   nsContentUtils::eFORMS_PROPERTIES,
                                   aWarningName,
                                   aWarningArgs, aWarningArgsLen);
 }
 
+static void
+RetrieveFileName(Blob* aBlob, nsAString& aFilename)
+{
+  MOZ_ASSERT(aBlob);
+
+  RefPtr<File> file = aBlob->ToFile();
+  if (file) {
+    file->GetName(aFilename);
+  }
+}
+
 // --------------------------------------------------------------------------
 
 class nsFSURLEncoded : public nsEncodingFormSubmission
 {
 public:
   /**
    * @param aCharset the charset of the form as a string
    * @param aMethod the method of the submit (either NS_FORM_METHOD_GET or
@@ -72,18 +83,18 @@ public:
       mMethod(aMethod),
       mDocument(aDocument),
       mWarnedFileControl(false)
   {
   }
 
   virtual nsresult AddNameValuePair(const nsAString& aName,
                                     const nsAString& aValue) override;
-  virtual nsresult AddNameFilePair(const nsAString& aName,
-                                   File* aFile) override;
+  virtual nsresult AddNameBlobPair(const nsAString& aName,
+                                   Blob* aBlob) override;
   virtual nsresult GetEncodedSubmission(nsIURI* aURI,
                                         nsIInputStream** aPostDataStream)
                                                                        override;
 
   virtual bool SupportsIsindexSubmission() override
   {
     return true;
   }
@@ -159,29 +170,28 @@ nsFSURLEncoded::AddIsindex(const nsAStri
   } else {
     mQueryString += NS_LITERAL_CSTRING("&isindex=") + convValue;
   }
 
   return NS_OK;
 }
 
 nsresult
-nsFSURLEncoded::AddNameFilePair(const nsAString& aName,
-                                File* aFile)
+nsFSURLEncoded::AddNameBlobPair(const nsAString& aName,
+                                Blob* aBlob)
 {
-  MOZ_ASSERT(aFile);
+  MOZ_ASSERT(aBlob);
 
   if (!mWarnedFileControl) {
     SendJSWarning(mDocument, "ForgotFileEnctypeWarning", nullptr, 0);
     mWarnedFileControl = true;
   }
 
   nsAutoString filename;
-  aFile->GetName(filename);
-
+  RetrieveFileName(aBlob, filename);
   return AddNameValuePair(aName, filename);
 }
 
 static void
 HandleMailtoSubject(nsCString& aPath) {
 
   // Walk through the string and see if we have a subject already.
   bool hasSubject = false;
@@ -433,61 +443,64 @@ nsFSMultipartFormData::AddNameValuePair(
                  + NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"")
                  + nameStr + NS_LITERAL_CSTRING("\"" CRLF CRLF)
                  + valueStr + NS_LITERAL_CSTRING(CRLF);
 
   return NS_OK;
 }
 
 nsresult
-nsFSMultipartFormData::AddNameFilePair(const nsAString& aName,
-                                       File* aFile)
+nsFSMultipartFormData::AddNameBlobPair(const nsAString& aName,
+                                       Blob* aBlob)
 {
-  MOZ_ASSERT(aFile);
+  MOZ_ASSERT(aBlob);
 
   // Encode the control name
   nsAutoCString nameStr;
   nsresult rv = EncodeVal(aName, nameStr, true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoString filename16;
-  aFile->GetName(filename16);
+  RetrieveFileName(aBlob, filename16);
 
   ErrorResult error;
   nsAutoString filepath16;
-  aFile->GetPath(filepath16, error);
-  if (NS_WARN_IF(error.Failed())) {
-    return error.StealNSResult();
+  RefPtr<File> file = aBlob->ToFile();
+  if (file) {
+    file->GetPath(filepath16, error);
+    if (NS_WARN_IF(error.Failed())) {
+      return error.StealNSResult();
+    }
   }
 
   if (!filepath16.IsEmpty()) {
     // File.path includes trailing "/"
     filename16 = filepath16 + filename16;
   }
 
   nsAutoCString filename;
   rv = EncodeVal(filename16, filename, true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Get content type
   nsAutoString contentType16;
-  aFile->GetType(contentType16);
+  aBlob->GetType(contentType16);
   if (contentType16.IsEmpty()) {
     contentType16.AssignLiteral("application/octet-stream");
   }
 
   nsAutoCString contentType;
   contentType.Adopt(nsLinebreakConverter::
                     ConvertLineBreaks(NS_ConvertUTF16toUTF8(contentType16).get(),
                                       nsLinebreakConverter::eLinebreakAny,
                                       nsLinebreakConverter::eLinebreakSpace));
 
   // Get input stream
   nsCOMPtr<nsIInputStream> fileStream;
-  aFile->GetInternalStream(getter_AddRefs(fileStream), error);
+  aBlob->GetInternalStream(getter_AddRefs(fileStream), error);
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
 
   if (fileStream) {
     // Create buffered stream (for efficiency)
     nsCOMPtr<nsIInputStream> bufferedStream;
     rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
@@ -512,17 +525,17 @@ nsFSMultipartFormData::AddNameFilePair(c
        + filename + NS_LITERAL_CSTRING("\"" CRLF)
        + NS_LITERAL_CSTRING("Content-Type: ")
        + contentType + NS_LITERAL_CSTRING(CRLF CRLF);
 
   // We should not try to append an invalid stream. That will happen for example
   // if we try to update a file that actually do not exist.
   if (fileStream) {
     ErrorResult error;
-    uint64_t size = aFile->GetSize(error);
+    uint64_t size = aBlob->GetSize(error);
     if (error.Failed()) {
       error.SuppressException();
     } else {
       // We need to dump the data up to this point into the POST data stream
       // here, since we're about to add the file input stream
       AddPostDataStream();
 
       mPostDataStream->AppendStream(fileStream);
@@ -585,18 +598,18 @@ class nsFSTextPlain : public nsEncodingF
 public:
   nsFSTextPlain(const nsACString& aCharset, nsIContent* aOriginatingElement)
     : nsEncodingFormSubmission(aCharset, aOriginatingElement)
   {
   }
 
   virtual nsresult AddNameValuePair(const nsAString& aName,
                                     const nsAString& aValue) override;
-  virtual nsresult AddNameFilePair(const nsAString& aName,
-                                   File* aFile) override;
+  virtual nsresult AddNameBlobPair(const nsAString& aName,
+                                   Blob* aBlob) override;
   virtual nsresult GetEncodedSubmission(nsIURI* aURI,
                                         nsIInputStream** aPostDataStream)
                                                                        override;
 
 private:
   nsString mBody;
 };
 
@@ -609,22 +622,23 @@ nsFSTextPlain::AddNameValuePair(const ns
   // values so we'll have to live with it.
   mBody.Append(aName + NS_LITERAL_STRING("=") + aValue +
                NS_LITERAL_STRING(CRLF));
 
   return NS_OK;
 }
 
 nsresult
-nsFSTextPlain::AddNameFilePair(const nsAString& aName,
-                               File* aFile)
+nsFSTextPlain::AddNameBlobPair(const nsAString& aName,
+                               Blob* aBlob)
 {
+  MOZ_ASSERT(aBlob);
+
   nsAutoString filename;
-  aFile->GetName(filename);
-
+  RetrieveFileName(aBlob, filename);
   AddNameValuePair(aName, filename);
   return NS_OK;
 }
 
 nsresult
 nsFSTextPlain::GetEncodedSubmission(nsIURI* aURI,
                                     nsIInputStream** aPostDataStream)
 {
--- a/dom/html/nsFormSubmission.h
+++ b/dom/html/nsFormSubmission.h
@@ -14,17 +14,17 @@
 
 class nsIURI;
 class nsIInputStream;
 class nsGenericHTMLElement;
 class nsIMultiplexInputStream;
 
 namespace mozilla {
 namespace dom {
-class File;
+class Blob;
 } // namespace dom
 } // namespace mozilla
 
 /**
  * Class for form submissions; encompasses the function to call to submit as
  * well as the form submission name/value pairs
  */
 class nsFormSubmission
@@ -40,23 +40,24 @@ public:
    *
    * @param aName the name of the parameter
    * @param aValue the value of the parameter
    */
   virtual nsresult AddNameValuePair(const nsAString& aName,
                                     const nsAString& aValue) = 0;
 
   /**
-   * Submit a name/file pair
+   * Submit a name/blob pair
    *
    * @param aName the name of the parameter
-   * @param aFile the file to submit. The file's name will be used
+   * @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.
    */
-  virtual nsresult AddNameFilePair(const nsAString& aName,
-                                   mozilla::dom::File* aFile) = 0;
+  virtual nsresult AddNameBlobPair(const nsAString& aName,
+                                   mozilla::dom::Blob* aBlob) = 0;
 
   /**
    * Reports whether the instance supports AddIsindex().
    *
    * @return true if supported.
    */
   virtual bool SupportsIsindexSubmission()
   {
@@ -154,18 +155,18 @@ public:
    * @param aCharset the charset of the form as a string
    */
   nsFSMultipartFormData(const nsACString& aCharset,
                         nsIContent* aOriginatingElement);
   ~nsFSMultipartFormData();
  
   virtual nsresult AddNameValuePair(const nsAString& aName,
                                     const nsAString& aValue) override;
-  virtual nsresult AddNameFilePair(const nsAString& aName,
-                                   mozilla::dom::File* aFile) override;
+  virtual nsresult AddNameBlobPair(const nsAString& aName,
+                                   mozilla::dom::Blob* aBlob) 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/test/formData_test.js
+++ b/dom/html/test/formData_test.js
@@ -76,17 +76,17 @@ function testSet() {
   is(f.getAll("other").length, 1, "set() should replace existing entries.");
   is(f.getAll("other")[0], "value4", "set() should replace existing entries.");
 }
 
 function testFilename() {
   var f = new FormData();
   // Spec says if a Blob (which is not a File) is added, the name parameter is set to "blob".
   f.append("blob", new Blob(["hi"]));
-  is(f.get("blob").name, "blob", "Blob's filename should be blob.");
+  ok(f.get("blob") instanceof Blob, "We should have a blob back.");
 
   // If a filename is passed, that should replace the original.
   f.append("blob2", new Blob(["hi"]), "blob2.txt");
   is(f.get("blob2").name, "blob2.txt", "Explicit filename should override \"blob\".");
 
   var file = new File(["hi"], "file1.txt");
   f.append("file1", file);
   // If a file is passed, the "create entry" algorithm should not create a new File, but reuse the existing one.
--- a/dom/html/test/test_formSubmission.html
+++ b/dom/html/test/test_formSubmission.html
@@ -585,17 +585,17 @@ var expectedAugment = [
   { name: "aNameNum", value: "9.2" },
   { name: "aNameFile1", value: placeholder_myFile1 },
   { name: "aNameFile2", value: placeholder_myFile2 },
   //{ name: "aNameObj", value: "[object XMLHttpRequest]" },
   //{ name: "aNameNull", value: "null" },
   //{ name: "aNameUndef", value: "undefined" },
 ];
 
-function checkMPSubmission(sub, expected, test) {
+function checkMPSubmission(sub, expected, test, isFormData = false) {
   function getPropCount(o) {
     var x, l = 0;
     for (x in o) ++l;
     return l;
   }
   function mpquote(s) {
     return s.replace(/\r\n/g, " ")
             .replace(/\r/g, " ")
@@ -620,17 +620,17 @@ function checkMPSubmission(sub, expected
           "Wrong number of headers in " + test);
       is(sub[i].body,
          expected[i].value.replace(/\r\n|\r|\n/, "\r\n"),
          "Correct value in " + test);
     }
     else {
       is(sub[i].headers["Content-Disposition"],
          "form-data; name=\"" + mpquote(expected[i].name) + "\"; filename=\"" +
-           mpquote(expected[i].fileName) + "\"",
+           mpquote(expected[i].fileName != "" || !isFormData ? expected[i].fileName : "blob") + "\"",
          "Correct name in " + test);
       is(sub[i].headers["Content-Type"],
          expected[i].contentType,
          "Correct content type in " + test);
       is (getPropCount(sub[i].headers), 2,
           "Wrong number of headers in " + test);
       is(sub[i].body,
          expected[i].value,
@@ -777,48 +777,48 @@ function runTest() {
   checkURLSubmission(submission, expectedSub);
 
   // Send form using XHR and FormData
   xhr = new XMLHttpRequest();
   xhr.onload = function() { gen.next(); };
   xhr.open("POST", "form_submit_server.sjs");
   xhr.send(new FormData(form));
   yield undefined; // Wait for XHR load
-  checkMPSubmission(JSON.parse(xhr.responseText), expectedSub, "send form using XHR and FormData");
+  checkMPSubmission(JSON.parse(xhr.responseText), expectedSub, "send form using XHR and FormData", true);
   
   // Send disabled form using XHR and FormData
   setDisabled(document.querySelectorAll("input, select, textarea"), true);
   xhr.open("POST", "form_submit_server.sjs");
   xhr.send(new FormData(form));
   yield undefined;
-  checkMPSubmission(JSON.parse(xhr.responseText), [], "send disabled form using XHR and FormData");
+  checkMPSubmission(JSON.parse(xhr.responseText), [], "send disabled form using XHR and FormData", true);
   setDisabled(document.querySelectorAll("input, select, textarea"), false);
   
   // Send FormData
   function addToFormData(fd) {
     fd.append("aName", "aValue");
     fd.append("aNameNum", 9.2);
     fd.append("aNameFile1", myFile1);
     fd.append("aNameFile2", myFile2);
   }
   var fd = new FormData();
   addToFormData(fd);
   xhr.open("POST", "form_submit_server.sjs");
   xhr.send(fd);
   yield undefined;
-  checkMPSubmission(JSON.parse(xhr.responseText), expectedAugment, "send FormData");
+  checkMPSubmission(JSON.parse(xhr.responseText), expectedAugment, "send FormData", true);
 
   // Augment <form> using FormData
   fd = new FormData(form);
   addToFormData(fd);
   xhr.open("POST", "form_submit_server.sjs");
   xhr.send(fd);
   yield undefined;
   checkMPSubmission(JSON.parse(xhr.responseText),
-                    expectedSub.concat(expectedAugment), "send augmented FormData");
+                    expectedSub.concat(expectedAugment), "send augmented FormData", true);
 
   SimpleTest.finish();
   yield undefined;
 }
 
 </script>
 </pre>
 </body>
--- 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 (File or USVString) FormDataEntryValue;
+typedef (Blob 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);