Bug 1085283 - Implement FormData manipulation methods. draft
authorNikhil Marathe <nsm.nikhil@gmail.com>
Wed, 28 Jan 2015 17:04:28 -0800
changeset 239454 b0f9f557cfbbdeb39eee90437a88910c2c517eea
parent 239185 38e4719e71af140cb95f04f7165d43a34c35a30d
child 239724 9eb24b9cfabf58c2ea1e6a0045b7045b387174c2
push id499
push usernsm.nikhil@gmail.com
push dateThu, 29 Jan 2015 01:20:07 +0000
bugs1085283
milestone38.0a1
Bug 1085283 - Implement FormData manipulation methods.
dom/base/nsFormData.cpp
dom/base/nsFormData.h
dom/html/test/test_formData.html
dom/webidl/FormData.webidl
--- a/dom/base/nsFormData.cpp
+++ b/dom/base/nsFormData.cpp
@@ -1,19 +1,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/. */
 
 #include "nsFormData.h"
 #include "nsIVariant.h"
 #include "nsIInputStream.h"
 #include "mozilla/dom/File.h"
-#include "mozilla/dom/File.h"
 #include "mozilla/dom/HTMLFormElement.h"
-#include "mozilla/dom/FormDataBinding.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsFormData::nsFormData(nsISupports* aOwner)
   : nsFormSubmission(NS_LITERAL_CSTRING("UTF-8"), nullptr)
   , mOwner(aOwner)
 {
@@ -81,16 +79,124 @@ nsFormData::Append(const nsAString& aNam
   if (aFilename.WasPassed()) {
     filename = aFilename.Value();
   } else {
     filename.SetIsVoid(true);
   }
   AddNameFilePair(aName, &aBlob, filename);
 }
 
+void
+nsFormData::Delete(const nsAString& aName)
+{
+  for (int32_t i = mFormData.Length() - 1; i >= 0; --i) {
+    if (aName.Equals(mFormData[i].name)) {
+      mFormData.RemoveElementAt(i);
+    }
+  }
+}
+
+void
+nsFormData::ExtractValue(const FormDataTuple& aTuple,
+                         OwningFileOrUSVString* aOutValue)
+{
+  if (aTuple.valueIsFile) {
+    nsRefPtr<File> file = static_cast<File*>(aTuple.fileValue.get());
+    aOutValue->SetAsFile() = file;
+  } else {
+    aOutValue->SetAsUSVString() = aTuple.stringValue;
+  }
+}
+
+void
+nsFormData::Get(const nsAString& aName,
+                Nullable<OwningFileOrUSVString>& aOutValue)
+{
+  for (uint32_t i = 0; i < mFormData.Length(); ++i) {
+    if (aName.Equals(mFormData[i].name)) {
+      ExtractValue(mFormData[i], &aOutValue.SetValue());
+      return;
+    }
+  }
+
+  aOutValue.SetNull();
+}
+
+void
+nsFormData::GetAll(const nsAString& aName,
+                   nsTArray<OwningFileOrUSVString>& aValues)
+{
+  for (uint32_t i = 0; i < mFormData.Length(); ++i) {
+    if (aName.Equals(mFormData[i].name)) {
+      OwningFileOrUSVString* element = aValues.AppendElement();
+      ExtractValue(mFormData[i], element);
+    }
+  }
+}
+
+bool
+nsFormData::Has(const nsAString& aName)
+{
+  for (uint32_t i = 0; i < mFormData.Length(); ++i) {
+    if (aName.Equals(mFormData[i].name)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+int32_t
+nsFormData::RemoveAllExceptFirstAndGetFirstIndex(const nsAString& aName)
+{
+  int32_t prevIndex = -1;
+  for (int32_t i = mFormData.Length() - 1; i >= 0; --i) {
+    if (aName.Equals(mFormData[i].name)) {
+      if (prevIndex >= 0) {
+        // The one we found earlier was not the first one, we can remove it.
+        mFormData.RemoveElementAt(prevIndex);
+      }
+
+      prevIndex = i;
+    }
+  }
+
+  return prevIndex;
+}
+
+void
+nsFormData::Set(const nsAString& aName, File& aBlob,
+                const Optional<nsAString>& aFilename)
+{
+  int32_t index = RemoveAllExceptFirstAndGetFirstIndex(aName);
+  if (index >= 0) {
+    nsAutoString filename;
+    if (aFilename.WasPassed()) {
+      filename = aFilename.Value();
+    } else {
+      filename.SetIsVoid(true);
+    }
+
+    SetNameFilePair(&mFormData[index], aName, &aBlob, filename);
+  } else {
+    Append(aName, aBlob, aFilename);
+  }
+}
+
+void
+nsFormData::Set(const nsAString& aName, const nsAString& aValue)
+{
+  int32_t index = RemoveAllExceptFirstAndGetFirstIndex(aName);
+  if (index >= 0) {
+    SetNameValuePair(&mFormData[index], aName, aValue);
+  } else {
+    Append(aName, aValue);
+  }
+}
+
 // -------------------------------------------------------------------------
 // nsIDOMFormData
 
 NS_IMETHODIMP
 nsFormData::Append(const nsAString& aName, nsIVariant* aValue)
 {
   uint16_t dataType;
   nsresult rv = aValue->GetDataType(&dataType);
--- a/dom/base/nsFormData.h
+++ b/dom/base/nsFormData.h
@@ -8,34 +8,74 @@
 #include "mozilla/Attributes.h"
 #include "nsIDOMFormData.h"
 #include "nsIXMLHttpRequest.h"
 #include "nsFormSubmission.h"
 #include "nsWrapperCache.h"
 #include "nsTArray.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/FormDataBinding.h"
 
 namespace mozilla {
 class ErrorResult;
 
 namespace dom {
 class File;
 class HTMLFormElement;
 class GlobalObject;
 } // namespace dom
 } // namespace mozilla
 
 class nsFormData MOZ_FINAL : public nsIDOMFormData,
                              public nsIXHRSendable,
                              public nsFormSubmission,
                              public nsWrapperCache
 {
+private:
   ~nsFormData() {}
 
+  struct FormDataTuple
+  {
+    nsString name;
+    nsString stringValue;
+    nsCOMPtr<nsIDOMBlob> fileValue;
+    nsString filename;
+    bool valueIsFile;
+  };
+
+  // Returns an index in which to modify element. This may be -1, in which case
+  // no element with aName was found.
+  int32_t
+  RemoveAllExceptFirstAndGetFirstIndex(const nsAString& aName);
+
+  void SetNameValuePair(FormDataTuple* aData,
+                        const nsAString& aName,
+                        const nsAString& aValue)
+  {
+    MOZ_ASSERT(aData);
+    aData->name = aName;
+    aData->stringValue = aValue;
+    aData->valueIsFile = false;
+  }
+
+  void SetNameFilePair(FormDataTuple* aData,
+                       const nsAString& aName,
+                       nsIDOMBlob* aBlob,
+                       const nsAString& aFilename)
+  {
+    MOZ_ASSERT(aData);
+    aData->name = aName;
+    aData->fileValue = aBlob;
+    aData->filename = aFilename;
+    aData->valueIsFile = true;
+  }
+
+  void ExtractValue(const FormDataTuple& aTuple,
+                    mozilla::dom::OwningFileOrUSVString* aOutValue);
 public:
   explicit nsFormData(nsISupports* aOwner = nullptr);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsFormData,
                                                          nsIDOMFormData)
 
   NS_DECL_NSIDOMFORMDATA
@@ -52,49 +92,41 @@ public:
   }
   static already_AddRefed<nsFormData>
   Constructor(const mozilla::dom::GlobalObject& aGlobal,
               const mozilla::dom::Optional<mozilla::dom::NonNull<mozilla::dom::HTMLFormElement> >& aFormElement,
               mozilla::ErrorResult& aRv);
   void Append(const nsAString& aName, const nsAString& aValue);
   void Append(const nsAString& aName, mozilla::dom::File& aBlob,
               const mozilla::dom::Optional<nsAString>& aFilename);
+  void Delete(const nsAString& aName);
+  void Get(const nsAString& aName, mozilla::dom::Nullable<mozilla::dom::OwningFileOrUSVString>& aOutValue);
+  void GetAll(const nsAString& aName, nsTArray<mozilla::dom::OwningFileOrUSVString>& aValues);
+  bool Has(const nsAString& aName);
+  void Set(const nsAString& aName, mozilla::dom::File& aBlob,
+           const mozilla::dom::Optional<nsAString>& aFilename);
+  void Set(const nsAString& aName, const nsAString& aValue);
 
   // nsFormSubmission
   virtual nsresult GetEncodedSubmission(nsIURI* aURI,
                                         nsIInputStream** aPostDataStream) MOZ_OVERRIDE;
   virtual nsresult AddNameValuePair(const nsAString& aName,
                                     const nsAString& aValue) MOZ_OVERRIDE
   {
     FormDataTuple* data = mFormData.AppendElement();
-    data->name = aName;
-    data->stringValue = aValue;
-    data->valueIsFile = false;
+    SetNameValuePair(data, aName, aValue);
     return NS_OK;
   }
   virtual nsresult AddNameFilePair(const nsAString& aName,
                                    nsIDOMBlob* aBlob,
                                    const nsString& aFilename) MOZ_OVERRIDE
   {
     FormDataTuple* data = mFormData.AppendElement();
-    data->name = aName;
-    data->fileValue = aBlob;
-    data->filename = aFilename;
-    data->valueIsFile = true;
+    SetNameFilePair(data, aName, aBlob, aFilename);
     return NS_OK;
   }
-
 private:
   nsCOMPtr<nsISupports> mOwner;
 
-  struct FormDataTuple
-  {
-    nsString name;
-    nsString stringValue;
-    nsCOMPtr<nsIDOMBlob> fileValue;
-    nsString filename;
-    bool valueIsFile;
-  };
-
   nsTArray<FormDataTuple> mFormData;
 };
 
 #endif // nsFormData_h__
--- a/dom/html/test/test_formData.html
+++ b/dom/html/test/test_formData.html
@@ -9,17 +9,100 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=690659">Mozilla Bug 690659</a>
 <script type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 
-function runTest() {
+function testHas() {
+  var f = new FormData();
+  f.append("foo", "bar");
+  f.append("another", "value");
+  ok(f.has("foo"), "has() on existing name should be true.");
+  ok(f.has("another"), "has() on existing name should be true.");
+  ok(!f.has("nonexistent"), "has() on non-existent name should be false.");
+}
+
+function testGet() {
+  var f = new FormData();
+  f.append("foo", "bar");
+  f.append("foo", "bar2");
+  f.append("blob", new Blob(["hey"], { type: 'text/plain' }));
+  f.append("file", new File(["hey"], 'testname',  {type: 'text/plain'}));
+
+  is(f.get("foo"), "bar", "get() on existing name should return first value");
+  ok(f.get("blob") instanceof Blob, "get() on existing name should return first value");
+  is(f.get("blob").type, 'text/plain', "get() on existing name should return first value");
+  ok(f.get("file") instanceof File, "get() on existing name should return first value");
+  is(f.get("file").name, 'testname', "get() on existing name should return first value");
+
+  is(f.get("nonexistent"), null, "get() on non-existent name should return null.");
+}
+
+function testGetAll() {
+  var f = new FormData();
+  f.append("other", "value");
+  f.append("foo", "bar");
+  f.append("foo", "bar2");
+  f.append("foo", new Blob(["hey"], { type: 'text/plain' }));
+
+  var arr = f.getAll("foo");
+  is(arr.length, 3, "getAll() should retrieve all matching entries.");
+  is(arr[0], "bar", "values should match and be in order");
+  is(arr[1], "bar2", "values should match and be in order");
+  ok(arr[2] instanceof Blob, "values should match and be in order");
+
+  is(f.get("nonexistent"), null, "get() on non-existent name should return null.");
+}
+
+function testDelete() {
+  var f = new FormData();
+  f.append("other", "value");
+  f.append("foo", "bar");
+  f.append("foo", "bar2");
+  f.append("foo", new Blob(["hey"], { type: 'text/plain' }));
+
+  ok(f.has("foo"), "has() on existing name should be true.");
+  f.delete("foo");
+  ok(!f.has("foo"), "has() on deleted name should be false.");
+  is(f.getAll("foo").length, 0, "all entries should be deleted.");
+
+  is(f.getAll("other").length, 1, "other names should still be there.");
+  f.delete("other");
+  is(f.getAll("other").length, 0, "all entries should be deleted.");
+}
+
+function testSet() {
+  var f = new FormData();
+
+  f.set("other", "value");
+  ok(f.has("other"), "set() on new name should be similar to append()");
+  is(f.getAll("other").length, 1, "set() on new name should be similar to append()");
+
+  f.append("other", "value2");
+  is(f.getAll("other").length, 2, "append() should not replace existing entries.");
+
+  f.append("foo", "bar");
+  f.append("other", "value3");
+  f.append("other", "value3");
+  f.append("other", "value3");
+  is(f.getAll("other").length, 5, "append() should not replace existing entries.");
+
+  f.set("other", "value4");
+  is(f.getAll("other").length, 1, "set() should replace existing entries.");
+  is(f.getAll("other")[0], "value4", "set() should replace existing entries.");
+}
+
+function testIterate() {
+  todo(false, "Implement this in Bug 1085284.");
+}
+
+function testSend() {
     var xhr = new XMLHttpRequest();
     xhr.open("POST", "form_submit_server.sjs");
     xhr.onload = function () {
         var response = xhr.response;
 
         for (var entry of response) {
             is(entry.body, 'hey');
             is(entry.headers['Content-Type'], 'text/plain');
@@ -57,12 +140,23 @@ function runTest() {
     file = new File([blob], '',  {type: 'text/plain'});
     fd.append("empty-file-name", file);
     file = new File([blob], 'testname',  {type: 'text/plain'});
     fd.append("file-name-overwrite", file, "overwrite");
     xhr.responseType = 'json';
     xhr.send(fd);
 }
 
+function runTest() {
+  testHas();
+  testGet();
+  testGetAll();
+  testDelete();
+  testSet();
+  testIterate();
+  // Finally, send an XHR and verify the response matches.
+  testSend();
+}
+
 runTest()
 </script>
 </body>
 </html>
--- a/dom/webidl/FormData.webidl
+++ b/dom/webidl/FormData.webidl
@@ -2,13 +2,22 @@
 /* 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;
+
 [Constructor(optional HTMLFormElement form)]
 interface FormData {
-  void append(DOMString name, Blob value, optional DOMString filename);
-  void append(DOMString name, DOMString value);
+  void append(USVString name, Blob value, optional USVString filename);
+  void append(USVString name, USVString value);
+  void delete(USVString name);
+  FormDataEntryValue? get(USVString name);
+  sequence<FormDataEntryValue> getAll(USVString name);
+  boolean has(USVString name);
+  void set(USVString name, Blob value, optional USVString filename);
+  void set(USVString name, USVString value);
+  // iterable<USVString, FormDataEntryValue>; - Bug 1085284
 };