Bug 739173 - Support FormData in workers. r=khuey,baku

--HG--
extra : rebase_source : 2ddd847d280e3aede963c51ed525170d86eebd3c
This commit is contained in:
Nikhil Marathe 2015-01-27 15:16:21 -08:00
parent f51e8c8dbd
commit 508af070e6
13 changed files with 456 additions and 174 deletions

View File

@ -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;

View 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);
}

View 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'});
});
}

View File

@ -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^

View File

@ -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>

View File

@ -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);

View File

@ -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)) {

View File

@ -1293,6 +1293,7 @@ GetCurrentThreadJSContext();
enum WorkerStructuredDataType
{
DOMWORKER_SCTAG_BLOB = SCTAG_DOM_MAX,
DOMWORKER_SCTAG_FORMDATA = SCTAG_DOM_MAX + 1,
DOMWORKER_SCTAG_END
};

View File

@ -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)
{

View File

@ -172,6 +172,9 @@ public:
void
Send(File& aBody, ErrorResult& aRv);
void
Send(nsFormData& aBody, ErrorResult& aRv);
void
Send(const ArrayBuffer& aBody, ErrorResult& aRv);

View File

@ -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!

View File

@ -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!

View File

@ -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