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 284547 0715e0b9a3167b0ac90c9f78f1195557ac12cd17
parent 284546 bf27afd3c5c65aa65df640a9b5da620326757930
child 284548 26ef2d27d757cf7bbd027b457c1863455a7a8309
push id71999
push useramarchesini@mozilla.com
push dateWed, 17 Feb 2016 14:56:36 +0000
treeherdermozilla-inbound@0715e0b9a316 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1246375
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 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');
     });
   });