Bug 1085283 - Implement FormData manipulation methods.
--- 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
};