Bug 1250148 - FormData should treat empty input type=file as empty string in FormData and as unnamed Blob in HTML submission, r=smaug
☠☠ backed out by 0f2230fd20b8 ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 23 Feb 2016 18:38:16 +0100
changeset 285179 74f968e3604168f1f124d3796aea8fce86a52fbb
parent 285178 a80acb104b38021e6051a839e4c2db86838b939c
child 285180 866f8a7337dfb9df08f8e85cb609f34a218c44d0
push id72260
push useramarchesini@mozilla.com
push dateTue, 23 Feb 2016 17:38:43 +0000
treeherdermozilla-inbound@74f968e36041 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1250148
milestone47.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 1250148 - FormData should treat empty input type=file as empty string in FormData and as unnamed Blob in HTML submission, r=smaug
dom/base/FormData.cpp
dom/base/FormData.h
dom/base/test/file_bug1250148.sjs
dom/base/test/mochitest.ini
dom/base/test/test_bug1250148.html
dom/html/HTMLInputElement.cpp
dom/html/nsFormSubmission.cpp
dom/html/nsFormSubmission.h
dom/html/test/test_formSubmission.html
--- a/dom/base/FormData.cpp
+++ b/dom/base/FormData.cpp
@@ -120,17 +120,17 @@ FormData::Append(const nsAString& aName,
                  const Optional<nsAString>& aFilename,
                  ErrorResult& aRv)
 {
   RefPtr<File> file = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
-  AddNameBlobPair(aName, file);
+  AddNameBlobOrNullPair(aName, file);
 }
 
 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; ) {
@@ -174,22 +174,28 @@ FormData::Has(const nsAString& aName)
       return true;
     }
   }
 
   return false;
 }
 
 nsresult
-FormData::AddNameBlobPair(const nsAString& aName, Blob* aBlob)
+FormData::AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob)
 {
-  MOZ_ASSERT(aBlob);
+  RefPtr<File> file;
+
+  if (!aBlob) {
+    FormDataTuple* data = mFormData.AppendElement();
+    SetNameValuePair(data, aName, EmptyString(), true /* aWasNullBlob */);
+    return NS_OK;
+  }
 
   ErrorResult rv;
-  RefPtr<File> file = GetOrCreateFileCalledBlob(*aBlob, rv);
+  file = GetOrCreateFileCalledBlob(*aBlob, rv);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
   FormDataTuple* data = mFormData.AppendElement();
   SetNameFilePair(data, aName, file);
   return NS_OK;
 }
@@ -264,20 +270,22 @@ FormData::GetValueAtIndex(uint32_t aInde
 {
   MOZ_ASSERT(aIndex < mFormData.Length());
   return mFormData[aIndex].value;
 }
 
 void
 FormData::SetNameValuePair(FormDataTuple* aData,
                            const nsAString& aName,
-                           const nsAString& aValue)
+                           const nsAString& aValue,
+                           bool aWasNullBlob)
 {
   MOZ_ASSERT(aData);
   aData->name = aName;
+  aData->wasNullBlob = aWasNullBlob;
   aData->value.SetAsUSVString() = aValue;
 }
 
 void
 FormData::SetNameFilePair(FormDataTuple* aData,
                           const nsAString& aName,
                           File* aFile)
 {
@@ -361,23 +369,26 @@ 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.IsBlob()) {
-      fs.AddNameBlobPair(mFormData[i].name, mFormData[i].value.GetAsBlob());
+    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_CRASH("This should no be possible.");
+      MOZ_ASSERT(mFormData[i].value.IsBlob());
+      fs.AddNameBlobOrNullPair(mFormData[i].name,
+                               mFormData[i].value.GetAsBlob());
     }
   }
 
   fs.GetContentType(aContentType);
   aCharset.Truncate();
   *aContentLength = 0;
   NS_ADDREF(*aBody = fs.GetSubmissionBody(aContentLength));
 
--- a/dom/base/FormData.h
+++ b/dom/base/FormData.h
@@ -30,27 +30,29 @@ class FormData final : public nsIDOMForm
                        public nsWrapperCache
 {
 private:
   ~FormData() {}
 
   struct FormDataTuple
   {
     nsString name;
+    bool wasNullBlob;
     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);
+                        const nsAString& aValue,
+                        bool aWasNullBlob = false);
 
   void SetNameFilePair(FormDataTuple* aData,
                        const nsAString& aName,
                        File* aFile);
 
 public:
   explicit FormData(nsISupports* aOwner = nullptr);
 
@@ -109,18 +111,18 @@ public:
   virtual nsresult AddNameValuePair(const nsAString& aName,
                                     const nsAString& aValue) override
   {
     FormDataTuple* data = mFormData.AppendElement();
     SetNameValuePair(data, aName, aValue);
     return NS_OK;
   }
 
-  virtual nsresult AddNameBlobPair(const nsAString& aName,
-                                   Blob* aBlob) override;
+  virtual nsresult AddNameBlobOrNullPair(const nsAString& aName,
+                                         Blob* aBlob) override;
 
   typedef bool (*FormDataEntryCallback)(const nsString& aName,
                                         const OwningBlobOrUSVString& aValue,
                                         void* aClosure);
 
   uint32_t
   Length() const
   {
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_bug1250148.sjs
@@ -0,0 +1,60 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+                             "nsIBinaryInputStream",
+                             "setInputStream");
+
+function utf8decode(s) {
+  return decodeURIComponent(escape(s));
+}
+
+function utf8encode(s) {
+  return unescape(encodeURIComponent(s));
+}
+
+function handleRequest(request, response) {
+  var bodyStream = new BinaryInputStream(request.bodyInputStream);
+
+  var requestBody = "";
+  while ((bodyAvail = bodyStream.available()) > 0) {
+    requestBody += bodyStream.readBytes(bodyAvail);
+  }
+
+  var result = [];
+
+  if (request.method == "POST") {
+    var contentTypeParams = {};
+    request.getHeader("Content-Type").split(/\s*\;\s*/).forEach(function(s) {
+      if (s.indexOf('=') >= 0) {
+        let [name, value] = s.split('=');
+        contentTypeParams[name] = value;
+      }
+      else {
+        contentTypeParams[''] = s;
+      }
+    });
+    
+    if (contentTypeParams[''] == "multipart/form-data" &&
+        request.queryString == "") {
+      requestBody.split("--" + contentTypeParams.boundary).slice(1, -1).forEach(function (s) {
+
+        let headers = {};
+        let headerEnd = s.indexOf("\r\n\r\n");
+        s.substr(2, headerEnd-2).split("\r\n").forEach(function(s) {
+          // We're assuming UTF8 for now
+          let [name, value] = s.split(': ');
+          headers[name] = utf8decode(value);
+        });
+
+        let body = s.substring(headerEnd + 4, s.length - 2);
+        if (!headers["Content-Type"] || headers["Content-Type"] == "text/plain") {
+          // We're assuming UTF8 for now
+          body = utf8decode(body);
+        }
+        result.push({ headers: headers, body: body});
+      });
+    }
+  }
+
+  response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+  response.write(utf8encode(JSON.stringify(result)));
+}
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -253,16 +253,17 @@ support-files =
   iframe_postMessages.html
   test_performance_observer.js
   performance_observer.html
   test_anonymousContent_style_csp.html^headers^
   file_explicit_user_agent.sjs
   referrer_change_server.sjs
   file_change_policy_redirect.html
   file_bug1198095.js
+  file_bug1250148.sjs
 
 [test_anonymousContent_api.html]
 [test_anonymousContent_append_after_reflow.html]
 [test_anonymousContent_canvas.html]
 skip-if = buildapp == 'b2g' # Requires webgl support
 [test_anonymousContent_insert.html]
 [test_anonymousContent_manipulate_content.html]
 [test_anonymousContent_style_csp.html]
@@ -865,8 +866,9 @@ support-files = worker_postMessages.js
 skip-if = e10s || os != 'linux' || buildapp != 'browser'
 [test_explicit_user_agent.html]
 [test_change_policy.html]
 skip-if = buildapp == 'b2g' #no ssl support
 [test_document.all_iteration.html]
 [test_bug1198095.html]
 [test_bug1187157.html]
 [test_bug769117.html]
+[test_bug1250148.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_bug1250148.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+  <!--
+       https://bugzilla.mozilla.org/show_bug.cgi?id=1250148
+     -->
+  <head>
+    <meta charset="utf-8">
+    <title>Test for Bug 1250148 - FormData and HTML submission compatibility</title>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  </head>
+  <body>
+    <form id="form" enctype="multipart/form-data"><input type="file" name="test" /></form>
+    <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var f = document.getElementById('form');
+var fd = new FormData(f);
+is(fd.get("test"), "", "We want an empty string.");
+
+var getAll = fd.getAll("test");
+ok(Array.isArray(getAll), "We want an array with 1 empty string.");
+is(getAll.length, 1, "We want an array with 1 empty string.");
+is(getAll[0], "", "We want an array with 1 empty string.");
+
+var xhr = new XMLHttpRequest();
+xhr.open("POST", "file_bug1250148.sjs", true);
+xhr.onload = function() {
+  var obj = JSON.parse(xhr.responseText);
+
+  ok(Array.isArray(obj), "XHR response is an array.");
+  is(obj.length, 1, "XHR response array contains 1 result.");
+
+  ok("headers" in obj[0], "XHR response has an header value");
+
+  ok("Content-Disposition" in obj[0].headers, "XHR response.headers array has a Content-Desposition value");
+  is(obj[0].headers["Content-Disposition"], "form-data; name=\"test\"; filename=\"\"", "Correct Content-Disposition");
+
+  ok("Content-Type" in obj[0].headers, "XHR response.headers array has a Content-Type value");
+  is(obj[0].headers["Content-Type"], "application/octet-stream", "Correct Content-Type");
+
+  ok("body" in obj[0], "XHR response has a body value");
+  is(obj[0].body, "", "Correct body value");
+
+  SimpleTest.finish();
+}
+xhr.send(fd);
+
+    </script>
+  </body>
+</html>
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -5577,24 +5577,21 @@ 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->AddNameBlobPair(name, files[i]);
+      aFormSubmission->AddNameBlobOrNullPair(name, files[i]);
     }
 
     if (files.IsEmpty()) {
-      RefPtr<BlobImpl> blobImpl =
-        new EmptyBlobImpl(NS_LITERAL_STRING("application/octet-stream"));
-      RefPtr<Blob> blob = Blob::Create(OwnerDoc()->GetInnerWindow(), blobImpl);
-      aFormSubmission->AddNameBlobPair(name, blob);
+      aFormSubmission->AddNameBlobOrNullPair(name, nullptr);
     }
 
     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
@@ -52,17 +52,19 @@ SendJSWarning(nsIDocument* aDocument,
                                   nsContentUtils::eFORMS_PROPERTIES,
                                   aWarningName,
                                   aWarningArgs, aWarningArgsLen);
 }
 
 static void
 RetrieveFileName(Blob* aBlob, nsAString& aFilename)
 {
-  MOZ_ASSERT(aBlob);
+  if (!aBlob) {
+    return;
+  }
 
   RefPtr<File> file = aBlob->ToFile();
   if (file) {
     file->GetName(aFilename);
   }
 }
 
 // --------------------------------------------------------------------------
@@ -83,18 +85,18 @@ public:
       mMethod(aMethod),
       mDocument(aDocument),
       mWarnedFileControl(false)
   {
   }
 
   virtual nsresult AddNameValuePair(const nsAString& aName,
                                     const nsAString& aValue) override;
-  virtual nsresult AddNameBlobPair(const nsAString& aName,
-                                   Blob* aBlob) override;
+  virtual nsresult AddNameBlobOrNullPair(const nsAString& aName,
+                                         Blob* aBlob) override;
   virtual nsresult GetEncodedSubmission(nsIURI* aURI,
                                         nsIInputStream** aPostDataStream)
                                                                        override;
 
   virtual bool SupportsIsindexSubmission() override
   {
     return true;
   }
@@ -170,21 +172,19 @@ nsFSURLEncoded::AddIsindex(const nsAStri
   } else {
     mQueryString += NS_LITERAL_CSTRING("&isindex=") + convValue;
   }
 
   return NS_OK;
 }
 
 nsresult
-nsFSURLEncoded::AddNameBlobPair(const nsAString& aName,
-                                Blob* aBlob)
+nsFSURLEncoded::AddNameBlobOrNullPair(const nsAString& aName,
+                                      Blob* aBlob)
 {
-  MOZ_ASSERT(aBlob);
-
   if (!mWarnedFileControl) {
     SendJSWarning(mDocument, "ForgotFileEnctypeWarning", nullptr, 0);
     mWarnedFileControl = true;
   }
 
   nsAutoString filename;
   RetrieveFileName(aBlob, filename);
   return AddNameValuePair(aName, filename);
@@ -431,98 +431,101 @@ nsFSMultipartFormData::AddNameValuePair(
 
   nsAutoCString nameStr;
   rv = EncodeVal(aName, nameStr, true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Make MIME block for name/value pair
 
   // XXX: name parameter should be encoded per RFC 2231
-  // RFC 2388 specifies that RFC 2047 be used, but I think it's not 
+  // RFC 2388 specifies that RFC 2047 be used, but I think it's not
   // consistent with MIME standard.
   mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary
                  + NS_LITERAL_CSTRING(CRLF)
                  + NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"")
                  + nameStr + NS_LITERAL_CSTRING("\"" CRLF CRLF)
                  + valueStr + NS_LITERAL_CSTRING(CRLF);
 
   return NS_OK;
 }
 
 nsresult
-nsFSMultipartFormData::AddNameBlobPair(const nsAString& aName,
-                                       Blob* aBlob)
+nsFSMultipartFormData::AddNameBlobOrNullPair(const nsAString& aName,
+                                             Blob* aBlob)
 {
-  MOZ_ASSERT(aBlob);
-
   // Encode the control name
   nsAutoCString nameStr;
   nsresult rv = EncodeVal(aName, nameStr, true);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsAutoString filename16;
-  RetrieveFileName(aBlob, filename16);
+  nsAutoCString filename;
+  nsAutoCString contentType;
+  nsCOMPtr<nsIInputStream> fileStream;
+
+  if (aBlob) {
+    nsAutoString filename16;
+    RetrieveFileName(aBlob, filename16);
+
+    ErrorResult error;
+    nsAutoString filepath16;
+    RefPtr<File> file = aBlob->ToFile();
+    if (file) {
+      file->GetPath(filepath16, error);
+      if (NS_WARN_IF(error.Failed())) {
+        return error.StealNSResult();
+      }
+    }
 
-  ErrorResult error;
-  nsAutoString filepath16;
-  RefPtr<File> file = aBlob->ToFile();
-  if (file) {
-    file->GetPath(filepath16, error);
+    if (!filepath16.IsEmpty()) {
+      // File.path includes trailing "/"
+      filename16 = filepath16 + filename16;
+    }
+
+    rv = EncodeVal(filename16, filename, true);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Get content type
+    nsAutoString contentType16;
+    aBlob->GetType(contentType16);
+    if (contentType16.IsEmpty()) {
+      contentType16.AssignLiteral("application/octet-stream");
+    }
+
+    contentType.Adopt(nsLinebreakConverter::
+                      ConvertLineBreaks(NS_ConvertUTF16toUTF8(contentType16).get(),
+                                        nsLinebreakConverter::eLinebreakAny,
+                                        nsLinebreakConverter::eLinebreakSpace));
+
+    // Get input stream
+    aBlob->GetInternalStream(getter_AddRefs(fileStream), 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;
-  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));
+    if (fileStream) {
+      // Create buffered stream (for efficiency)
+      nsCOMPtr<nsIInputStream> bufferedStream;
+      rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
+                                     fileStream, 8192);
+      NS_ENSURE_SUCCESS(rv, rv);
 
-  // Get input stream
-  nsCOMPtr<nsIInputStream> fileStream;
-  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),
-                                   fileStream, 8192);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    fileStream = bufferedStream;
+      fileStream = bufferedStream;
+    }
+  } else {
+    contentType.AssignLiteral("application/octet-stream");
   }
 
   //
   // Make MIME block for name/value pair
   //
   // more appropriate than always using binary?
   mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary
                  + NS_LITERAL_CSTRING(CRLF);
   // XXX: name/filename parameter should be encoded per RFC 2231
-  // RFC 2388 specifies that RFC 2047 be used, but I think it's not 
+  // RFC 2388 specifies that RFC 2047 be used, but I think it's not
   // consistent with the MIME standard.
   mPostDataChunk +=
          NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"")
        + nameStr + NS_LITERAL_CSTRING("\"; filename=\"")
        + filename + NS_LITERAL_CSTRING("\"" CRLF)
        + NS_LITERAL_CSTRING("Content-Type: ")
        + contentType + NS_LITERAL_CSTRING(CRLF CRLF);
 
@@ -571,17 +574,17 @@ nsFSMultipartFormData::GetEncodedSubmiss
 
   return NS_OK;
 }
 
 nsresult
 nsFSMultipartFormData::AddPostDataStream()
 {
   nsresult rv = NS_OK;
-  
+
   nsCOMPtr<nsIInputStream> postDataChunkStream;
   rv = NS_NewCStringInputStream(getter_AddRefs(postDataChunkStream),
                                 mPostDataChunk);
   NS_ASSERTION(postDataChunkStream, "Could not open a stream for POST!");
   if (postDataChunkStream) {
     mPostDataStream->AppendStream(postDataChunkStream);
     mTotalLength += mPostDataChunk.Length();
   }
@@ -598,18 +601,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 AddNameBlobPair(const nsAString& aName,
-                                   Blob* aBlob) override;
+  virtual nsresult AddNameBlobOrNullPair(const nsAString& aName,
+                                         Blob* aBlob) override;
   virtual nsresult GetEncodedSubmission(nsIURI* aURI,
                                         nsIInputStream** aPostDataStream)
                                                                        override;
 
 private:
   nsString mBody;
 };
 
@@ -622,21 +625,19 @@ 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::AddNameBlobPair(const nsAString& aName,
-                               Blob* aBlob)
+nsFSTextPlain::AddNameBlobOrNullPair(const nsAString& aName,
+                                     Blob* aBlob)
 {
-  MOZ_ASSERT(aBlob);
-
   nsAutoString filename;
   RetrieveFileName(aBlob, filename);
   AddNameValuePair(aName, filename);
   return NS_OK;
 }
 
 nsresult
 nsFSTextPlain::GetEncodedSubmission(nsIURI* aURI,
--- a/dom/html/nsFormSubmission.h
+++ b/dom/html/nsFormSubmission.h
@@ -44,20 +44,21 @@ public:
   virtual nsresult AddNameValuePair(const nsAString& aName,
                                     const nsAString& aValue) = 0;
 
   /**
    * Submit a name/blob pair
    *
    * @param aName the name of the parameter
    * @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.
+   * is actually a File, otherwise 'blob' string is used instead if the aBlob is
+   * not null.
    */
-  virtual nsresult AddNameBlobPair(const nsAString& aName,
-                                   mozilla::dom::Blob* aBlob) = 0;
+  virtual nsresult AddNameBlobOrNullPair(const nsAString& aName,
+                                         mozilla::dom::Blob* aBlob) = 0;
 
   /**
    * Reports whether the instance supports AddIsindex().
    *
    * @return true if supported.
    */
   virtual bool SupportsIsindexSubmission()
   {
@@ -155,18 +156,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 AddNameBlobPair(const nsAString& aName,
-                                   mozilla::dom::Blob* aBlob) override;
+  virtual nsresult AddNameBlobOrNullPair(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/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, isFormData = false) {
+function checkMPSubmission(sub, expected, test) {
   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 != "" || !isFormData ? expected[i].fileName : "blob") + "\"",
+           mpquote(expected[i].fileName) + "\"",
          "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", true);
+  checkMPSubmission(JSON.parse(xhr.responseText), expectedSub, "send form using XHR and FormData");
   
   // 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", true);
+  checkMPSubmission(JSON.parse(xhr.responseText), [], "send disabled form using XHR and FormData");
   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", true);
+  checkMPSubmission(JSON.parse(xhr.responseText), expectedAugment, "send FormData");
 
   // 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", true);
+                    expectedSub.concat(expectedAugment), "send augmented FormData");
 
   SimpleTest.finish();
   yield undefined;
 }
 
 </script>
 </pre>
 </body>