mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-28 21:28:55 +00:00
Bug 739173 - Support FormData in workers. r=khuey,baku
--HG-- extra : rebase_source : 2ddd847d280e3aede963c51ed525170d86eebd3c
This commit is contained in:
parent
f51e8c8dbd
commit
508af070e6
@ -120,6 +120,33 @@ public:
|
||||
SetNameFilePair(data, aName, aBlob);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
typedef bool (*FormDataEntryCallback)(const nsString& aName, bool aIsFile,
|
||||
const nsString& aValue,
|
||||
File* aFile, void* aClosure);
|
||||
|
||||
uint32_t
|
||||
Length() const
|
||||
{
|
||||
return mFormData.Length();
|
||||
}
|
||||
|
||||
// Stops iteration and returns false if any invocation of callback returns
|
||||
// false. Returns true otherwise.
|
||||
bool
|
||||
ForEach(FormDataEntryCallback aFunc, void* aClosure)
|
||||
{
|
||||
for (uint32_t i = 0; i < mFormData.Length(); ++i) {
|
||||
FormDataTuple& tuple = mFormData[i];
|
||||
if (!aFunc(tuple.name, tuple.valueIsFile, tuple.stringValue,
|
||||
tuple.fileValue, aClosure)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsISupports> mOwner;
|
||||
|
||||
|
167
dom/html/test/formData_test.js
Normal file
167
dom/html/test/formData_test.js
Normal file
@ -0,0 +1,167 @@
|
||||
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 testFilename() {
|
||||
var f = new FormData();
|
||||
// Spec says if a Blob (which is not a File) is added, the name parameter is set to "blob".
|
||||
f.append("blob", new Blob(["hi"]));
|
||||
is(f.get("blob").name, "blob", "Blob's filename should be blob.");
|
||||
|
||||
// If a filename is passed, that should replace the original.
|
||||
f.append("blob2", new Blob(["hi"]), "blob2.txt");
|
||||
is(f.get("blob2").name, "blob2.txt", "Explicit filename should override \"blob\".");
|
||||
|
||||
var file = new File(["hi"], "file1.txt");
|
||||
f.append("file1", file);
|
||||
// If a file is passed, the "create entry" algorithm should not create a new File, but reuse the existing one.
|
||||
is(f.get("file1"), file, "Retrieved File object should be original File object and not a copy.");
|
||||
is(f.get("file1").name, "file1.txt", "File's filename should be original's name if no filename is explicitly passed.");
|
||||
|
||||
file = new File(["hi"], "file2.txt");
|
||||
f.append("file2", file, "fakename.txt");
|
||||
ok(f.get("file2") !== file, "Retrieved File object should be new File object if explicit filename is passed.");
|
||||
is(f.get("file2").name, "fakename.txt", "File's filename should be explicitly passed name.");
|
||||
f.append("file3", new File(["hi"], ""));
|
||||
is(f.get("file3").name, "", "File's filename is returned even if empty.");
|
||||
}
|
||||
|
||||
function testSend(doneCb) {
|
||||
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');
|
||||
}
|
||||
|
||||
is(response[0].headers['Content-Disposition'],
|
||||
'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'],
|
||||
'form-data; name="file-name"; filename="testname"');
|
||||
|
||||
is(response[4].headers['Content-Disposition'],
|
||||
'form-data; name="empty-file-name"; filename=""');
|
||||
|
||||
is(response[5].headers['Content-Disposition'],
|
||||
'form-data; name="file-name-overwrite"; filename="overwrite"');
|
||||
|
||||
doneCb();
|
||||
}
|
||||
|
||||
var file, blob = new Blob(['hey'], {type: 'text/plain'});
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append("empty", blob);
|
||||
fd.append("explicit", blob, "explicit-file-name");
|
||||
fd.append("explicit-empty", blob, "");
|
||||
file = new File([blob], 'testname', {type: 'text/plain'});
|
||||
fd.append("file-name", file);
|
||||
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(doneCb) {
|
||||
testHas();
|
||||
testGet();
|
||||
testGetAll();
|
||||
testDelete();
|
||||
testSet();
|
||||
testIterate();
|
||||
testFilename();
|
||||
// Finally, send an XHR and verify the response matches.
|
||||
testSend(doneCb);
|
||||
}
|
||||
|
19
dom/html/test/formData_worker.js
Normal file
19
dom/html/test/formData_worker.js
Normal file
@ -0,0 +1,19 @@
|
||||
function ok(a, msg) {
|
||||
postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
|
||||
}
|
||||
|
||||
function is(a, b, msg) {
|
||||
postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
|
||||
}
|
||||
|
||||
function todo(a, msg) {
|
||||
postMessage({type: 'todo', status: !!a, msg: a + ": " + msg });
|
||||
}
|
||||
|
||||
importScripts("formData_test.js");
|
||||
|
||||
onmessage = function() {
|
||||
runTest(function() {
|
||||
postMessage({type: 'finish'});
|
||||
});
|
||||
}
|
@ -179,6 +179,8 @@ support-files =
|
||||
file_window_open_close_outer.html
|
||||
file_window_open_close_inner.html
|
||||
form_submit_server.sjs
|
||||
formData_worker.js
|
||||
formData_test.js
|
||||
image.png
|
||||
image-allow-credentials.png
|
||||
image-allow-credentials.png^headers^
|
||||
|
@ -4,183 +4,47 @@
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=690659
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 690659</title>
|
||||
<title>Test for Bug 690659 and 739173</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<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>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=690659">Mozilla Bug 690659 & 739173</a>
|
||||
<script type="text/javascript" src="./formData_test.js"></script>
|
||||
<script type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
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 runMainThreadAndWorker() {
|
||||
var mt = new Promise(function(resolve) {
|
||||
runTest(resolve);
|
||||
});
|
||||
|
||||
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 testFilename() {
|
||||
var f = new FormData();
|
||||
// Spec says if a Blob (which is not a File) is added, the name parameter is set to blob.
|
||||
f.append("blob", new Blob(["hi"]));
|
||||
is(f.get("blob").name, "blob", "Blob's filename should be blob.");
|
||||
|
||||
// If a filename is passed, that should replace the original.
|
||||
f.append("blob2", new Blob(["hi"]), "blob2.txt");
|
||||
is(f.get("blob2").name, "blob2.txt", "Explicit filename should override \"blob\".");
|
||||
|
||||
var file = new File(["hi"], "file1.txt");
|
||||
f.append("file1", file);
|
||||
// If a file is passed, the "create entry" algorithm should not create a new File, but reuse the existing one.
|
||||
is(f.get("file1"), file, "Retrieved File object should be original File object and not a copy.");
|
||||
is(f.get("file1").name, "file1.txt", "File's filename should be original's name if no filename is explicitly passed.");
|
||||
file = new File(["hi"], "file2.txt");
|
||||
f.append("file2", file, "fakename.txt");
|
||||
ok(f.get("file2") !== file, "Retrieved File object should be new File object if explicit filename is passed.");
|
||||
is(f.get("file2").name, "fakename.txt", "File's filename should be explicitly passed name.");
|
||||
f.append("file3", new File(["hi"], ""));
|
||||
is(f.get("file3").name, "", "File's filename is returned even if empty.");
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
is(response[0].headers['Content-Disposition'],
|
||||
'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'],
|
||||
'form-data; name="file-name"; filename="testname"');
|
||||
|
||||
is(response[4].headers['Content-Disposition'],
|
||||
'form-data; name="empty-file-name"; filename=""');
|
||||
|
||||
is(response[5].headers['Content-Disposition'],
|
||||
'form-data; name="file-name-overwrite"; filename="overwrite"');
|
||||
|
||||
SimpleTest.finish();
|
||||
var worker;
|
||||
var w = new Promise(function(resolve) {
|
||||
worker = new Worker("formData_worker.js");
|
||||
worker.onmessage = function(event) {
|
||||
if (event.data.type == 'finish') {
|
||||
resolve();
|
||||
} else if (event.data.type == 'status') {
|
||||
ok(event.data.status, event.data.msg);
|
||||
} else if (event.data.type == 'todo') {
|
||||
todo(event.data.status, event.data.msg);
|
||||
}
|
||||
}
|
||||
|
||||
var file, blob = new Blob(['hey'], {type: 'text/plain'});
|
||||
worker.onerror = function(event) {
|
||||
ok(false, "Worker had an error: " + event.message + " at " + event.lineno);
|
||||
resolve();
|
||||
};
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append("empty", blob);
|
||||
fd.append("explicit", blob, "explicit-file-name");
|
||||
fd.append("explicit-empty", blob, "");
|
||||
file = new File([blob], 'testname', {type: 'text/plain'});
|
||||
fd.append("file-name", file);
|
||||
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);
|
||||
worker.postMessage(true);
|
||||
});
|
||||
|
||||
return Promise.all([mt, w]);
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
testHas();
|
||||
testGet();
|
||||
testGetAll();
|
||||
testDelete();
|
||||
testSet();
|
||||
testIterate();
|
||||
testFilename();
|
||||
// Finally, send an XHR and verify the response matches.
|
||||
testSend();
|
||||
}
|
||||
|
||||
runTest()
|
||||
runMainThreadAndWorker().then(SimpleTest.finish);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -9,7 +9,8 @@
|
||||
|
||||
typedef (File or USVString) FormDataEntryValue;
|
||||
|
||||
[Constructor(optional HTMLFormElement form)]
|
||||
[Constructor(optional HTMLFormElement form),
|
||||
Exposed=(Window,Worker)]
|
||||
interface FormData {
|
||||
void append(USVString name, Blob value, optional USVString filename);
|
||||
void append(USVString name, USVString value);
|
||||
|
@ -56,6 +56,7 @@
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/dom/StructuredClone.h"
|
||||
#include "mozilla/dom/WebCryptoCommon.h"
|
||||
#include "mozilla/dom/WorkerBinding.h"
|
||||
#include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
|
||||
#include "mozilla/dom/WorkerGlobalScopeBinding.h"
|
||||
@ -71,6 +72,7 @@
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsError.h"
|
||||
#include "nsDOMJSUtils.h"
|
||||
#include "nsFormData.h"
|
||||
#include "nsHostObjectProtocolHandler.h"
|
||||
#include "nsJSEnvironment.h"
|
||||
#include "nsJSUtils.h"
|
||||
@ -391,15 +393,13 @@ EnsureBlobForBackgroundManager(FileImpl* aBlobImpl,
|
||||
return blobImpl.forget();
|
||||
}
|
||||
|
||||
void
|
||||
ReadBlobOrFile(JSContext* aCx,
|
||||
JSStructuredCloneReader* aReader,
|
||||
bool aIsMainThread,
|
||||
JS::MutableHandle<JSObject*> aBlobOrFile)
|
||||
already_AddRefed<File>
|
||||
ReadBlobOrFileNoWrap(JSContext* aCx,
|
||||
JSStructuredCloneReader* aReader,
|
||||
bool aIsMainThread)
|
||||
{
|
||||
MOZ_ASSERT(aCx);
|
||||
MOZ_ASSERT(aReader);
|
||||
MOZ_ASSERT(!aBlobOrFile);
|
||||
|
||||
nsRefPtr<FileImpl> blobImpl;
|
||||
{
|
||||
@ -433,9 +433,77 @@ ReadBlobOrFile(JSContext* aCx,
|
||||
}
|
||||
|
||||
nsRefPtr<File> blob = new File(parent, blobImpl);
|
||||
return blob.forget();
|
||||
}
|
||||
|
||||
void
|
||||
ReadBlobOrFile(JSContext* aCx,
|
||||
JSStructuredCloneReader* aReader,
|
||||
bool aIsMainThread,
|
||||
JS::MutableHandle<JSObject*> aBlobOrFile)
|
||||
{
|
||||
nsRefPtr<File> blob = ReadBlobOrFileNoWrap(aCx, aReader, aIsMainThread);
|
||||
aBlobOrFile.set(blob->WrapObject(aCx));
|
||||
}
|
||||
|
||||
// See WriteFormData for serialization format.
|
||||
void
|
||||
ReadFormData(JSContext* aCx,
|
||||
JSStructuredCloneReader* aReader,
|
||||
bool aIsMainThread,
|
||||
uint32_t aCount,
|
||||
JS::MutableHandle<JSObject*> aFormData)
|
||||
{
|
||||
MOZ_ASSERT(aCx);
|
||||
MOZ_ASSERT(aReader);
|
||||
MOZ_ASSERT(!aFormData);
|
||||
|
||||
nsCOMPtr<nsISupports> parent;
|
||||
if (aIsMainThread) {
|
||||
AssertIsOnMainThread();
|
||||
nsCOMPtr<nsIScriptGlobalObject> scriptGlobal =
|
||||
nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(aCx));
|
||||
parent = do_QueryInterface(scriptGlobal);
|
||||
} else {
|
||||
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
|
||||
MOZ_ASSERT(workerPrivate);
|
||||
workerPrivate->AssertIsOnWorkerThread();
|
||||
|
||||
WorkerGlobalScope* globalScope = workerPrivate->GlobalScope();
|
||||
MOZ_ASSERT(globalScope);
|
||||
|
||||
parent = do_QueryObject(globalScope);
|
||||
}
|
||||
|
||||
nsRefPtr<nsFormData> formData = new nsFormData(parent);
|
||||
MOZ_ASSERT(formData);
|
||||
|
||||
Optional<nsAString> thirdArg;
|
||||
|
||||
uint32_t isFile;
|
||||
uint32_t dummy;
|
||||
for (uint32_t i = 0; i < aCount; ++i) {
|
||||
MOZ_ALWAYS_TRUE(JS_ReadUint32Pair(aReader, &isFile, &dummy));
|
||||
|
||||
nsAutoString name;
|
||||
MOZ_ALWAYS_TRUE(ReadString(aReader, name));
|
||||
|
||||
if (isFile) {
|
||||
// Read out the tag since the blob reader isn't expecting it.
|
||||
MOZ_ALWAYS_TRUE(JS_ReadUint32Pair(aReader, &dummy, &dummy));
|
||||
nsRefPtr<File> blob = ReadBlobOrFileNoWrap(aCx, aReader, aIsMainThread);
|
||||
MOZ_ASSERT(blob);
|
||||
formData->Append(name, *blob, thirdArg);
|
||||
} else {
|
||||
nsAutoString value;
|
||||
MOZ_ALWAYS_TRUE(ReadString(aReader, value));
|
||||
formData->Append(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
aFormData.set(formData->WrapObject(aCx));
|
||||
}
|
||||
|
||||
bool
|
||||
WriteBlobOrFile(JSContext* aCx,
|
||||
JSStructuredCloneWriter* aWriter,
|
||||
@ -462,6 +530,72 @@ WriteBlobOrFile(JSContext* aCx,
|
||||
return true;
|
||||
}
|
||||
|
||||
// A FormData is serialized as:
|
||||
// - A pair of ints (tag identifying it as a FormData, number of elements in
|
||||
// the FormData)
|
||||
// - for each (key, value) pair:
|
||||
// - pair of ints (is value a file?, 0). If not a file, value is a string.
|
||||
// - string name
|
||||
// - if value is a file:
|
||||
// - write the file/blob
|
||||
// - else:
|
||||
// - string value
|
||||
bool
|
||||
WriteFormData(JSContext* aCx,
|
||||
JSStructuredCloneWriter* aWriter,
|
||||
nsFormData* aFormData,
|
||||
nsTArray<nsCOMPtr<nsISupports>>& aClonedObjects)
|
||||
{
|
||||
MOZ_ASSERT(aCx);
|
||||
MOZ_ASSERT(aWriter);
|
||||
MOZ_ASSERT(aFormData);
|
||||
|
||||
if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, DOMWORKER_SCTAG_FORMDATA, aFormData->Length()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
class MOZ_STACK_CLASS Closure {
|
||||
JSContext* mCx;
|
||||
JSStructuredCloneWriter* mWriter;
|
||||
nsTArray<nsCOMPtr<nsISupports>>& mClones;
|
||||
|
||||
public:
|
||||
Closure(JSContext* aCx, JSStructuredCloneWriter* aWriter,
|
||||
nsTArray<nsCOMPtr<nsISupports>>& aClones)
|
||||
: mCx(aCx), mWriter(aWriter), mClones(aClones)
|
||||
{ }
|
||||
|
||||
static bool
|
||||
Write(const nsString& aName, bool isFile, const nsString& aValue,
|
||||
File* aFile, void* aClosure)
|
||||
{
|
||||
Closure* closure = static_cast<Closure*>(aClosure);
|
||||
if (!JS_WriteUint32Pair(closure->mWriter, /* a file? */ (uint32_t) isFile, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!WriteString(closure->mWriter, aName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isFile) {
|
||||
if (!WriteBlobOrFile(closure->mCx, closure->mWriter, aFile->Impl(), closure->mClones)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!WriteString(closure->mWriter, aValue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
Closure closure(aCx, aWriter, aClonedObjects);
|
||||
return aFormData->ForEach(Closure::Write, &closure);
|
||||
}
|
||||
|
||||
struct WorkerStructuredCloneCallbacks
|
||||
{
|
||||
static JSObject*
|
||||
@ -484,6 +618,13 @@ struct WorkerStructuredCloneCallbacks
|
||||
MOZ_ASSERT(!aData);
|
||||
return ReadStructuredCloneImageData(aCx, aReader);
|
||||
}
|
||||
// See if the object is a FormData.
|
||||
else if (aTag == DOMWORKER_SCTAG_FORMDATA) {
|
||||
JS::Rooted<JSObject*> formData(aCx);
|
||||
// aData is the entry count.
|
||||
ReadFormData(aCx, aReader, /* aIsMainThread */ false, aData, &formData);
|
||||
return formData;
|
||||
}
|
||||
|
||||
Error(aCx, 0);
|
||||
return nullptr;
|
||||
@ -520,6 +661,16 @@ struct WorkerStructuredCloneCallbacks
|
||||
}
|
||||
}
|
||||
|
||||
// See if this is a FormData object.
|
||||
{
|
||||
nsFormData* formData = nullptr;
|
||||
if (NS_SUCCEEDED(UNWRAP_OBJECT(FormData, aObj, formData))) {
|
||||
if (WriteFormData(aCx, aWriter, formData, *clonedObjects)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Error(aCx, 0);
|
||||
return false;
|
||||
}
|
||||
@ -557,6 +708,13 @@ struct MainThreadWorkerStructuredCloneCallbacks
|
||||
|
||||
return blobOrFile;
|
||||
}
|
||||
// See if the object is a FormData.
|
||||
else if (aTag == DOMWORKER_SCTAG_FORMDATA) {
|
||||
JS::Rooted<JSObject*> formData(aCx);
|
||||
// aData is the entry count.
|
||||
ReadFormData(aCx, aReader, /* aIsMainThread */ true, aData, &formData);
|
||||
return formData;
|
||||
}
|
||||
|
||||
JS_ClearPendingException(aCx);
|
||||
return NS_DOMReadStructuredClone(aCx, aReader, aTag, aData, nullptr);
|
||||
@ -3158,7 +3316,7 @@ WorkerPrivateParent<Derived>::PostMessageInternal(
|
||||
transferable.setObject(*array);
|
||||
}
|
||||
|
||||
nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
|
||||
nsTArray<nsCOMPtr<nsISupports>> clonedObjects;
|
||||
|
||||
JSAutoStructuredCloneBuffer buffer;
|
||||
if (!buffer.write(aCx, aMessage, transferable, callbacks, &clonedObjects)) {
|
||||
|
@ -1293,6 +1293,7 @@ GetCurrentThreadJSContext();
|
||||
enum WorkerStructuredDataType
|
||||
{
|
||||
DOMWORKER_SCTAG_BLOB = SCTAG_DOM_MAX,
|
||||
DOMWORKER_SCTAG_FORMDATA = SCTAG_DOM_MAX + 1,
|
||||
|
||||
DOMWORKER_SCTAG_END
|
||||
};
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "mozilla/dom/ProgressEvent.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsFormData.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
@ -2199,6 +2200,44 @@ XMLHttpRequest::Send(File& aBody, ErrorResult& aRv)
|
||||
SendInternal(EmptyString(), Move(buffer), clonedObjects, aRv);
|
||||
}
|
||||
|
||||
void
|
||||
XMLHttpRequest::Send(nsFormData& aBody, ErrorResult& aRv)
|
||||
{
|
||||
mWorkerPrivate->AssertIsOnWorkerThread();
|
||||
JSContext* cx = mWorkerPrivate->GetJSContext();
|
||||
|
||||
if (mCanceled) {
|
||||
aRv.ThrowUncatchableException();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mProxy) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> value(cx);
|
||||
if (!GetOrCreateDOMReflector(cx, &aBody, &value)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
const JSStructuredCloneCallbacks* callbacks =
|
||||
mWorkerPrivate->IsChromeWorker() ?
|
||||
ChromeWorkerStructuredCloneCallbacks(false) :
|
||||
WorkerStructuredCloneCallbacks(false);
|
||||
|
||||
nsTArray<nsCOMPtr<nsISupports>> clonedObjects;
|
||||
|
||||
JSAutoStructuredCloneBuffer buffer;
|
||||
if (!buffer.write(cx, value, callbacks, &clonedObjects)) {
|
||||
aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
SendInternal(EmptyString(), Move(buffer), clonedObjects, aRv);
|
||||
}
|
||||
|
||||
void
|
||||
XMLHttpRequest::Send(const ArrayBuffer& aBody, ErrorResult& aRv)
|
||||
{
|
||||
|
@ -172,6 +172,9 @@ public:
|
||||
void
|
||||
Send(File& aBody, ErrorResult& aRv);
|
||||
|
||||
void
|
||||
Send(nsFormData& aBody, ErrorResult& aRv);
|
||||
|
||||
void
|
||||
Send(const ArrayBuffer& aBody, ErrorResult& aRv);
|
||||
|
||||
|
@ -114,6 +114,8 @@ var interfaceNamesInGlobalScope =
|
||||
"File",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"FileReaderSync",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"FormData",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"Headers",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
|
@ -108,6 +108,8 @@ var interfaceNamesInGlobalScope =
|
||||
"File",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"FileReaderSync",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"FormData",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"Headers",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
|
@ -15,9 +15,6 @@
|
||||
[The ProgressEvent interface object should be exposed.]
|
||||
expected: FAIL
|
||||
|
||||
[The FormData interface object should be exposed.]
|
||||
expected: FAIL
|
||||
|
||||
[The CanvasProxy interface object should be exposed.]
|
||||
expected: FAIL
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user