Bug 1246375 - Restore the previous spec version of FormData, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 17 Feb 2016 14:56:19 +0000
changeset 331615 0715e0b9a3167b0ac90c9f78f1195557ac12cd17
parent 331614 bf27afd3c5c65aa65df640a9b5da620326757930
child 331616 26ef2d27d757cf7bbd027b457c1863455a7a8309
push id11020
push userjolesen@mozilla.com
push dateWed, 17 Feb 2016 18:16:38 +0000
reviewerssmaug
bugs1246375
milestone47.0a1
Bug 1246375 - Restore the previous spec version of FormData, r=smaug
dom/base/FormData.cpp
dom/base/FormData.h
dom/base/test/fileutils.js
dom/html/test/formData_test.js
dom/html/test/test_formSubmission.html
dom/tests/mochitest/fetch/test_fetch_basic_http.js
dom/tests/mochitest/fetch/test_request.js
testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html
testing/web-platform/tests/XMLHttpRequest/formdata-blob.htm
--- a/dom/base/FormData.cpp
+++ b/dom/base/FormData.cpp
@@ -18,33 +18,51 @@ using namespace mozilla::dom;
 FormData::FormData(nsISupports* aOwner)
   : nsFormSubmission(NS_LITERAL_CSTRING("UTF-8"), nullptr)
   , mOwner(aOwner)
 {
 }
 
 namespace {
 
-already_AddRefed<Blob>
-GetBlobForFormDataStorage(Blob& aBlob, const Optional<nsAString>& aFilename,
-                          ErrorResult& aRv)
+already_AddRefed<File>
+GetOrCreateFileCalledBlob(Blob& aBlob, ErrorResult& aRv)
 {
-  if (!aFilename.WasPassed()) {
-    RefPtr<Blob> blob = &aBlob;
-    return blob.forget();
+  // If this is file, we can just use it
+  RefPtr<File> file = aBlob.ToFile();
+  if (file) {
+    return file.forget();
   }
 
-  RefPtr<File> file = aBlob.ToFile(aFilename.Value(), aRv);
+  // Forcing 'blob' as filename
+  file = aBlob.ToFile(NS_LITERAL_STRING("blob"), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   return file.forget();
 }
 
+already_AddRefed<File>
+GetBlobForFormDataStorage(Blob& aBlob, const Optional<nsAString>& aFilename,
+                          ErrorResult& aRv)
+{
+  // Forcing a filename
+  if (aFilename.WasPassed()) {
+    RefPtr<File> file = aBlob.ToFile(aFilename.Value(), aRv);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+
+    return file.forget();
+  }
+
+  return GetOrCreateFileCalledBlob(aBlob, aRv);
+}
+
 } // namespace
 
 // -------------------------------------------------------------------------
 // nsISupports
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(FormData)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FormData)
@@ -97,22 +115,22 @@ FormData::Append(const nsAString& aName,
   AddNameValuePair(aName, aValue);
 }
 
 void
 FormData::Append(const nsAString& aName, Blob& aBlob,
                  const Optional<nsAString>& aFilename,
                  ErrorResult& aRv)
 {
-  RefPtr<Blob> blob = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
+  RefPtr<File> file = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
-  AddNameBlobPair(aName, blob);
+  AddNameBlobPair(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; ) {
@@ -160,18 +178,24 @@ FormData::Has(const nsAString& aName)
   return false;
 }
 
 nsresult
 FormData::AddNameBlobPair(const nsAString& aName, Blob* aBlob)
 {
   MOZ_ASSERT(aBlob);
 
+  ErrorResult rv;
+  RefPtr<File> file = GetOrCreateFileCalledBlob(*aBlob, rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    return rv.StealNSResult();
+  }
+
   FormDataTuple* data = mFormData.AppendElement();
-  SetNameBlobPair(data, aName, aBlob);
+  SetNameFilePair(data, aName, file);
   return NS_OK;
 }
 
 FormData::FormDataTuple*
 FormData::RemoveAllOthersAndGetFirstFormDataTuple(const nsAString& aName)
 {
   FormDataTuple* lastFoundTuple = nullptr;
   uint32_t lastFoundIndex = mFormData.Length();
@@ -194,22 +218,22 @@ FormData::RemoveAllOthersAndGetFirstForm
 
 void
 FormData::Set(const nsAString& aName, Blob& aBlob,
               const Optional<nsAString>& aFilename,
               ErrorResult& aRv)
 {
   FormDataTuple* tuple = RemoveAllOthersAndGetFirstFormDataTuple(aName);
   if (tuple) {
-    RefPtr<Blob> blob = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
+    RefPtr<File> file = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
     if (NS_WARN_IF(aRv.Failed())) {
       return;
     }
 
-    SetNameBlobPair(tuple, aName, blob);
+    SetNameFilePair(tuple, aName, file);
   } else {
     Append(aName, aBlob, aFilename, aRv);
   }
 }
 
 void
 FormData::Set(const nsAString& aName, const nsAString& aValue,
               ErrorResult& aRv)
@@ -248,25 +272,25 @@ FormData::SetNameValuePair(FormDataTuple
                            const nsAString& aValue)
 {
   MOZ_ASSERT(aData);
   aData->name = aName;
   aData->value.SetAsUSVString() = aValue;
 }
 
 void
-FormData::SetNameBlobPair(FormDataTuple* aData,
+FormData::SetNameFilePair(FormDataTuple* aData,
                           const nsAString& aName,
-                          Blob* aBlob)
+                          File* aFile)
 {
   MOZ_ASSERT(aData);
-  MOZ_ASSERT(aBlob);
+  MOZ_ASSERT(aFile);
 
   aData->name = aName;
-  aData->value.SetAsBlob() = aBlob;
+  aData->value.SetAsBlob() = aFile;
 }
 
 // -------------------------------------------------------------------------
 // nsIDOMFormData
 
 NS_IMETHODIMP
 FormData::Append(const nsAString& aName, nsIVariant* aValue)
 {
--- a/dom/base/FormData.h
+++ b/dom/base/FormData.h
@@ -42,19 +42,19 @@ private:
   // no element with aName was found.
   FormDataTuple*
   RemoveAllOthersAndGetFirstFormDataTuple(const nsAString& aName);
 
   void SetNameValuePair(FormDataTuple* aData,
                         const nsAString& aName,
                         const nsAString& aValue);
 
-  void SetNameBlobPair(FormDataTuple* aData,
+  void SetNameFilePair(FormDataTuple* aData,
                        const nsAString& aName,
-                       Blob* aBlob);
+                       File* aFile);
 
 public:
   explicit FormData(nsISupports* aOwner = nullptr);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(FormData,
                                                          nsIDOMFormData)
 
--- a/dom/base/test/fileutils.js
+++ b/dom/base/test/fileutils.js
@@ -31,17 +31,17 @@ function testFile(file, contents, test) 
 
   // Send file to server using FormData and XMLHttpRequest
   xhr = new XMLHttpRequest();
   xhr.onload = function(event) {
     checkMPSubmission(JSON.parse(event.target.responseText),
                       [{ name: "hello", value: "world"},
                        { name: "myfile",
                          value: contents,
-                         fileName: file.name || "",
+                         fileName: file.name || "blob",
                          contentType: file.type || "application/octet-stream" }]);
     testHasRun();
   }
   xhr.open("POST", "../../../dom/html/test/form_submit_server.sjs");
   var fd = new FormData;
   fd.append("hello", "world");
   fd.append("myfile", file);
   xhr.send(fd);
--- a/dom/html/test/formData_test.js
+++ b/dom/html/test/formData_test.js
@@ -157,17 +157,17 @@ function testSend(doneCb) {
     var response = xhr.response;
 
     for (var entry of response) {
       is(entry.body, 'hey');
       is(entry.headers['Content-Type'], 'text/plain');
     }
 
     is(response[0].headers['Content-Disposition'],
-        'form-data; name="empty"; filename=""');
+        'form-data; name="empty"; filename="blob"');
 
     is(response[1].headers['Content-Disposition'],
         'form-data; name="explicit"; filename="explicit-file-name"');
 
     is(response[2].headers['Content-Disposition'],
         'form-data; name="explicit-empty"; filename=""');
 
     is(response[3].headers['Content-Disposition'],
--- 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/tests/mochitest/fetch/test_fetch_basic_http.js
+++ b/dom/tests/mochitest/fetch/test_fetch_basic_http.js
@@ -164,17 +164,17 @@ function testFormDataSend() {
         if (entry.headers['Content-Disposition'] != 'form-data; name="string"') {
           is(entry.headers['Content-Type'], 'text/plain');
         }
 
         is(entry.body, 'hey');
       }
 
       is(response[1].headers['Content-Disposition'],
-          'form-data; name="empty"; filename=""');
+          'form-data; name="empty"; filename="blob"');
 
       is(response[2].headers['Content-Disposition'],
           'form-data; name="explicit"; filename="explicit-file-name"');
 
       is(response[3].headers['Content-Disposition'],
           'form-data; name="explicit-empty"; filename=""');
 
       is(response[4].headers['Content-Disposition'],
--- a/dom/tests/mochitest/fetch/test_request.js
+++ b/dom/tests/mochitest/fetch/test_request.js
@@ -339,16 +339,17 @@ function testFormDataBodyCreation() {
   f1.append("blob", new Blob([text]));
   var r2 = new Request("", { method: 'post', body: f1 });
   f1.delete("key");
   var p2 = r2.formData().then(function(fd) {
     ok(fd instanceof FormData, "Valid FormData extracted.");
     ok(fd.has("more"), "more should exist.");
 
     var b = fd.get("blob");
+    ok(b.name, "blob", "blob entry should be a Blob.");
     ok(b instanceof Blob, "blob entry should be a Blob.");
 
     return readAsText(b).then(function(output) {
       is(output, text, "Blob contents should match.");
     });
   });
 
   return Promise.all([p1, p2]);
@@ -412,27 +413,29 @@ function testFormDataBodyExtraction() {
   f1.append("blob", new Blob([text]));
   var r2 = new Request("", { method: 'post', body: f1 });
   var p2 = r2.formData().then(function(fd) {
     ok(fd.has("key"), "Has entry 'key'.");
     ok(fd.has("foo"), "Has entry 'foo'.");
     ok(fd.has("blob"), "Has entry 'blob'.");
     var entries = fd.getAll("blob");
     is(entries.length, 1, "getAll returns all items.");
+    is(entries[0].name, "blob", "Filename should be blob.");
     ok(entries[0] instanceof Blob, "getAll returns blobs.");
   });
 
   var ws = "\r\n\r\n\r\n\r\n";
   f1.set("key", new File([ws], 'file name has spaces.txt', { type: 'new/lines' }));
   var r3 = new Request("", { method: 'post', body: f1 });
   var p3 = r3.formData().then(function(fd) {
     ok(fd.has("foo"), "Has entry 'foo'.");
     ok(fd.has("blob"), "Has entry 'blob'.");
     var entries = fd.getAll("blob");
     is(entries.length, 1, "getAll returns all items.");
+    is(entries[0].name, "blob", "Filename should be blob.");
     ok(entries[0] instanceof Blob, "getAll returns blobs.");
 
     ok(fd.has("key"), "Has entry 'key'.");
     var f = fd.get("key");
     ok(f instanceof File, "entry should be a File.");
     is(f.name, "file name has spaces.txt", "File name should match.");
     is(f.type, "new/lines", "File type should match.");
     is(f.size, ws.length, "File size should match.");
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/fetch-request-xhr-iframe.https.html
@@ -103,17 +103,17 @@ function form_data_test() {
         var boundary = get_boundary(response.headers);
         var expected_body =
           '--' + boundary + '\r\n' +
           'Content-Disposition: form-data; name="sample string"\r\n' +
           '\r\n' +
           '1234567890\r\n' +
           '--' + boundary + '\r\n' +
           'Content-Disposition: form-data; name="sample blob"; ' +
-          'filename=""\r\n' +
+          'filename="blob"\r\n' +
           'Content-Type: application/octet-stream\r\n' +
           '\r\n' +
           'blob content\r\n' +
           '--' + boundary + '\r\n' +
           'Content-Disposition: form-data; name="sample file"; ' +
           'filename="file.dat"\r\n' +
           'Content-Type: application/octet-stream\r\n' +
           '\r\n' +
--- a/testing/web-platform/tests/XMLHttpRequest/formdata-blob.htm
+++ b/testing/web-platform/tests/XMLHttpRequest/formdata-blob.htm
@@ -28,17 +28,17 @@
   function create_formdata () {
     var fd = new FormData();
     for (var i = 0; i < arguments.length; i++) {
       fd.append.apply(fd, arguments[i]);
     }
     return fd;
   }
 
-  do_test("formdata with blob", create_formdata(['key', new Blob(['value'], {type: 'text/x-value'})]), 'key=value,\n');
+  do_test("formdata with blob", create_formdata(['key', new Blob(['value'], {type: 'text/x-value'})]), '\nkey=blob:text/x-value:5,');
   do_test("formdata with named blob", create_formdata(['key', new Blob(['value'], {type: 'text/x-value'}), 'blob.txt']), '\nkey=blob.txt:text/x-value:5,');
   // If 3rd argument is given and 2nd is not a Blob, formdata.append() should throw
   var test = async_test('formdata.append() should throw if value is string and file name is given'); // needs to be async just because the others above are
   test.step(function(){
     assert_throws(new TypeError(), function(){
       create_formdata('a', 'b', 'c');
     });
   });