Bug 1162658 - Update FormData to match latest spec, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 20 Jan 2016 17:25:03 +0000
changeset 303026 1079f6d91f65f24c401133e6c313c7f405749c82
parent 303025 8b78eccf2c296d54da1d8ed02d1293a8ff6138e3
child 303027 0f7510fa983c1804792cb4a43d341b6b4e533172
push id8978
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 14:05:32 +0000
treeherdermozilla-aurora@b9a803752a2c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1162658
milestone46.0a1
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);