Bug 720083 - Workers: add support for transferable objects from HTML5 spec. r=sphink, r=bent

This commit is contained in:
Andrea Marchesini 2012-09-27 23:05:59 -04:00
parent be7032627b
commit 7e21557bd5
16 changed files with 539 additions and 89 deletions

View File

@ -12,6 +12,13 @@ try {
inWorker = true;
}
function message(data) {
if (inWorker)
self.postMessage(data);
else
self.postMessage(data, "*");
}
function is(got, expected, msg) {
var obj = {};
obj.type = "is";
@ -19,7 +26,7 @@ function is(got, expected, msg) {
obj.expected = expected;
obj.msg = msg;
self.postMessage(obj, "*");
message(obj);
}
function ok(bool, msg) {
@ -28,7 +35,7 @@ function ok(bool, msg) {
obj.bool = bool;
obj.msg = msg;
self.postMessage(obj, "*");
message(obj);
}
/**
@ -323,7 +330,7 @@ var TestCounter = {
test.startXHR();
}
else {
self.postMessage("done", "*");
message("done");
}
}
};

View File

@ -51,7 +51,7 @@ nsStructuredCloneContainer::InitFromVariant(nsIVariant *aData, JSContext *aCx)
// Make sure that we serialize in the right context.
JSAutoRequest ar(aCx);
JSAutoCompartment ac(aCx, JS_GetGlobalObject(aCx));
JSAutoCompartment ac(aCx, JS_GetGlobalObject(aCx));
JS_WrapValue(aCx, &jsData);
nsCxPusher cxPusher;
@ -59,7 +59,7 @@ nsStructuredCloneContainer::InitFromVariant(nsIVariant *aData, JSContext *aCx)
uint64_t* jsBytes = nullptr;
bool success = JS_WriteStructuredClone(aCx, jsData, &jsBytes, &mSize,
nullptr, nullptr);
nullptr, nullptr, JSVAL_VOID);
NS_ENSURE_STATE(success);
NS_ENSURE_STATE(jsBytes);
@ -69,9 +69,7 @@ nsStructuredCloneContainer::InitFromVariant(nsIVariant *aData, JSContext *aCx)
mSize = 0;
mVersion = 0;
// FIXME This should really be js::Foreground::Free, but that's not public.
JS_free(aCx, jsBytes);
JS_ClearStructuredClone(jsBytes, mSize);
return NS_ERROR_FAILURE;
}
else {
@ -80,8 +78,7 @@ nsStructuredCloneContainer::InitFromVariant(nsIVariant *aData, JSContext *aCx)
memcpy(mData, jsBytes, mSize);
// FIXME Similarly, this should be js::Foreground::free.
JS_free(aCx, jsBytes);
JS_ClearStructuredClone(jsBytes, mSize);
return NS_OK;
}
@ -119,9 +116,14 @@ nsStructuredCloneContainer::DeserializeToVariant(JSContext *aCx,
// Deserialize to a jsval.
jsval jsStateObj;
JSBool hasTransferable;
bool success = JS_ReadStructuredClone(aCx, mData, mSize, mVersion,
&jsStateObj, nullptr, nullptr);
NS_ENSURE_STATE(success);
&jsStateObj, nullptr, nullptr) &&
JS_StructuredCloneHasTransferables(mData, mSize,
&hasTransferable);
// We want to be sure that mData doesn't contain transferable objects
MOZ_ASSERT(!hasTransferable);
NS_ENSURE_STATE(success && !hasTransferable);
// Now wrap the jsval as an nsIVariant.
nsCOMPtr<nsIVariant> varStateObj;

View File

@ -166,7 +166,7 @@ namespace mozilla {
namespace dom {
bool
ReadStructuredClone(JSContext* aCx, const uint64_t* aData, size_t aDataLength,
ReadStructuredClone(JSContext* aCx, uint64_t* aData, size_t aDataLength,
const StructuredCloneClosure& aClosure, JS::Value* aClone)
{
void* closure = &const_cast<StructuredCloneClosure&>(aClosure);

View File

@ -34,7 +34,7 @@ StructuredCloneData
};
bool
ReadStructuredClone(JSContext* aCx, const uint64_t* aData, size_t aDataLength,
ReadStructuredClone(JSContext* aCx, uint64_t* aData, size_t aDataLength,
const StructuredCloneClosure& aClosure, JS::Value* aClone);
inline bool

View File

@ -286,11 +286,13 @@ private:
}
jsval message;
if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", &message)) {
jsval transferable = JSVAL_VOID;
if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v/v",
&message, &transferable)) {
return false;
}
return worker->PostMessage(aCx, message);
return worker->PostMessage(aCx, message, transferable);
}
};

View File

@ -2231,7 +2231,8 @@ WorkerPrivateParent<Derived>::ForgetMainThreadObjects(
template <class Derived>
bool
WorkerPrivateParent<Derived>::PostMessage(JSContext* aCx, jsval aMessage)
WorkerPrivateParent<Derived>::PostMessage(JSContext* aCx, jsval aMessage,
jsval aTransferable)
{
AssertIsOnParentThread();
@ -2265,7 +2266,7 @@ WorkerPrivateParent<Derived>::PostMessage(JSContext* aCx, jsval aMessage)
nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
JSAutoStructuredCloneBuffer buffer;
if (!buffer.write(aCx, aMessage, callbacks, &clonedObjects)) {
if (!buffer.write(aCx, aMessage, aTransferable, callbacks, &clonedObjects)) {
return false;
}
@ -3422,7 +3423,8 @@ WorkerPrivate::StopSyncLoop(uint32_t aSyncLoopKey, bool aSyncResult)
}
bool
WorkerPrivate::PostMessageToParent(JSContext* aCx, jsval aMessage)
WorkerPrivate::PostMessageToParent(JSContext* aCx, jsval aMessage,
jsval aTransferable)
{
AssertIsOnWorkerThread();
@ -3434,7 +3436,7 @@ WorkerPrivate::PostMessageToParent(JSContext* aCx, jsval aMessage)
nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
JSAutoStructuredCloneBuffer buffer;
if (!buffer.write(aCx, aMessage, callbacks, &clonedObjects)) {
if (!buffer.write(aCx, aMessage, aTransferable, callbacks, &clonedObjects)) {
return false;
}

View File

@ -294,7 +294,7 @@ public:
ForgetMainThreadObjects(nsTArray<nsCOMPtr<nsISupports> >& aDoomed);
bool
PostMessage(JSContext* aCx, jsval aMessage);
PostMessage(JSContext* aCx, jsval aMessage, jsval aTransferable);
uint64_t
GetInnerWindowId();
@ -662,7 +662,8 @@ public:
StopSyncLoop(uint32_t aSyncLoopKey, bool aSyncResult);
bool
PostMessageToParent(JSContext* aCx, jsval aMessage);
PostMessageToParent(JSContext* aCx, jsval aMessage,
jsval transferable);
bool
NotifyInternal(JSContext* aCx, Status aStatus);

View File

@ -839,11 +839,13 @@ private:
}
jsval message;
if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", &message)) {
jsval transferable = JSVAL_VOID;
if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v/v",
&message, &transferable)) {
return false;
}
return scope->mWorker->PostMessageToParent(aCx, message);
return scope->mWorker->PostMessageToParent(aCx, message, transferable);
}
};

View File

@ -100,6 +100,8 @@ MOCHITEST_FILES = \
test_csp.js \
test_csp.html^headers^ \
csp_worker.js \
test_transferable.html \
transferable_worker.js \
$(NULL)
_SUBDIRMOCHITEST_FILES = \

View File

@ -0,0 +1,75 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<!--
Tests of DOM Worker transferable objects
-->
<head>
<title>Test for DOM Worker transferable objects</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" language="javascript">
function test1(sizes) {
if (!sizes.length) {
SimpleTest.finish();
return;
}
var size = sizes.pop();
var worker = new Worker("transferable_worker.js");
worker.onmessage = function(event) {
ok(event.data.status, event.data.event);
if (!event.data.status) {
SimpleTest.finish();
return;
}
if (!event.data.last)
return;
test1(sizes);
}
worker.onerror = function(event) {
ok(false, "No errors!");
}
try {
worker.postMessage(42, true);
ok(false, "P: PostMessage - Exception for wrong type");
} catch(e) {
ok(true, "P: PostMessage - Exception for wrong type");
}
try {
ab = new ArrayBuffer(size);
worker.postMessage(42,[ab, ab]);
ok(false, "P: PostMessage - Exception for duplicate");
} catch(e) {
ok(true, "P: PostMessage - Exception for duplicate");
}
ab = new ArrayBuffer(size);
ok(ab.byteLength == size, "P: The size is: " + size + " == " + ab.byteLength);
worker.postMessage({ data: 0, timeout: 0, ab: ab, cb: ab, size: size }, [ab]);
ok(ab.byteLength == 0, "P: PostMessage - The size is: 0 == " + ab.byteLength)
}
test1([1024 * 1024 * 32, 128, 4]);
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,33 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
onmessage = function(event) {
if (event.data.data == 0) {
ab = new ArrayBuffer(event.data.size);
postMessage({ event: "W: The size is: " + event.data.size + " == " + ab.byteLength,
status: ab.byteLength == event.data.size, last: false });
postMessage({ event: "W: postMessage with arrayBuffer", status: true,
ab: ab, bc: [ ab, ab, { dd: ab } ] }, [ab]);
postMessage({ event: "W: The size is: 0 == " + ab.byteLength,
status: ab.byteLength == 0, last: false });
postMessage({ event: "last one!", status: true, last: true });
} else {
var worker = new Worker('sync_worker.js');
worker.onmessage = function(event) {
postMessage(event.data);
}
worker.onsyncmessage = function(event) {
var v = postSyncMessage(event.data, null, 500);
event.reply(v);
}
--event.data.data;
worker.postMessage(event.data);
}
}

View File

@ -6406,7 +6406,7 @@ JS_ParseJSONWithReviver(JSContext *cx, const jschar *chars, uint32_t len, jsval
}
JS_PUBLIC_API(JSBool)
JS_ReadStructuredClone(JSContext *cx, const uint64_t *buf, size_t nbytes,
JS_ReadStructuredClone(JSContext *cx, uint64_t *buf, size_t nbytes,
uint32_t version, jsval *vp,
const JSStructuredCloneCallbacks *optionalCallbacks,
void *closure)
@ -6428,7 +6428,7 @@ JS_ReadStructuredClone(JSContext *cx, const uint64_t *buf, size_t nbytes,
JS_PUBLIC_API(JSBool)
JS_WriteStructuredClone(JSContext *cx, jsval valueArg, uint64_t **bufp, size_t *nbytesp,
const JSStructuredCloneCallbacks *optionalCallbacks,
void *closure)
void *closure, jsval transferable)
{
RootedValue value(cx, valueArg);
AssertHeapIsIdle(cx);
@ -6439,7 +6439,26 @@ JS_WriteStructuredClone(JSContext *cx, jsval valueArg, uint64_t **bufp, size_t *
optionalCallbacks ?
optionalCallbacks :
cx->runtime->structuredCloneCallbacks;
return WriteStructuredClone(cx, value, (uint64_t **) bufp, nbytesp, callbacks, closure);
return WriteStructuredClone(cx, valueArg, (uint64_t **) bufp, nbytesp,
callbacks, closure, transferable);
}
JS_PUBLIC_API(JSBool)
JS_ClearStructuredClone(const uint64_t *data, size_t nbytes)
{
return ClearStructuredClone(data, nbytes);
}
JS_PUBLIC_API(JSBool)
JS_StructuredCloneHasTransferables(const uint64_t *data, size_t nbytes,
JSBool *hasTransferable)
{
bool transferable;
if (!StructuredCloneHasTransferObjects(data, nbytes, &transferable))
return false;
*hasTransferable = transferable;
return true;
}
JS_PUBLIC_API(JSBool)
@ -6465,7 +6484,7 @@ void
JSAutoStructuredCloneBuffer::clear()
{
if (data_) {
js_free(data_);
ClearStructuredClone(data_, nbytes_);
data_ = NULL;
nbytes_ = 0;
version_ = 0;
@ -6484,6 +6503,12 @@ JSAutoStructuredCloneBuffer::adopt(uint64_t *data, size_t nbytes, uint32_t versi
bool
JSAutoStructuredCloneBuffer::copy(const uint64_t *srcData, size_t nbytes, uint32_t version)
{
// transferable objects cannot be copied
bool hasTransferable;
if (!StructuredCloneHasTransferObjects(data_, nbytes_, &hasTransferable) ||
hasTransferable)
return false;
uint64_t *newData = static_cast<uint64_t *>(js_malloc(nbytes));
if (!newData)
return false;
@ -6512,7 +6537,7 @@ JSAutoStructuredCloneBuffer::steal(uint64_t **datap, size_t *nbytesp, uint32_t *
bool
JSAutoStructuredCloneBuffer::read(JSContext *cx, jsval *vp,
const JSStructuredCloneCallbacks *optionalCallbacks,
void *closure) const
void *closure)
{
JS_ASSERT(cx);
JS_ASSERT(data_);
@ -6524,11 +6549,22 @@ bool
JSAutoStructuredCloneBuffer::write(JSContext *cx, jsval valueArg,
const JSStructuredCloneCallbacks *optionalCallbacks,
void *closure)
{
jsval transferable = JSVAL_VOID;
return write(cx, valueArg, transferable, optionalCallbacks, closure);
}
bool
JSAutoStructuredCloneBuffer::write(JSContext *cx, jsval valueArg,
jsval transferable,
const JSStructuredCloneCallbacks *optionalCallbacks,
void *closure)
{
RootedValue value(cx, valueArg);
clear();
bool ok = !!JS_WriteStructuredClone(cx, value, &data_, &nbytes_,
optionalCallbacks, closure);
optionalCallbacks, closure,
transferable);
if (!ok) {
data_ = NULL;
nbytes_ = 0;

View File

@ -2043,9 +2043,9 @@ typedef JSBool (*WriteStructuredCloneOp)(JSContext *cx, JSStructuredCloneWriter
JSObject *obj, void *closure);
/*
* This is called when JS_WriteStructuredClone finds that the object to be
* written is recursive. To follow HTML5, the application must throw a
* DATA_CLONE_ERR DOMException. errorid is always JS_SCERR_RECURSION.
* This is called when JS_WriteStructuredClone is given an invalid transferable.
* To follow HTML5, the application must throw a DATA_CLONE_ERR DOMException
* with error set to one of the JS_SCERR_* values.
*/
typedef void (*StructuredCloneErrorOp)(JSContext *cx, uint32_t errorid);
@ -5705,17 +5705,27 @@ struct JSStructuredCloneCallbacks {
StructuredCloneErrorOp reportError;
};
/* Note: if the *data contains transferable objects, it can be read
* only once */
JS_PUBLIC_API(JSBool)
JS_ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes,
JS_ReadStructuredClone(JSContext *cx, uint64_t *data, size_t nbytes,
uint32_t version, jsval *vp,
const JSStructuredCloneCallbacks *optionalCallbacks,
void *closure);
/* Note: On success, the caller is responsible for calling js::Foreground::free(*datap). */
/* Note: On success, the caller is responsible for calling
* JS_ClearStructuredClone(*datap, nbytesp). */
JS_PUBLIC_API(JSBool)
JS_WriteStructuredClone(JSContext *cx, jsval v, uint64_t **datap, size_t *nbytesp,
const JSStructuredCloneCallbacks *optionalCallbacks,
void *closure);
void *closure, jsval transferable);
JS_PUBLIC_API(JSBool)
JS_ClearStructuredClone(const uint64_t *data, size_t nbytes);
JS_PUBLIC_API(JSBool)
JS_StructuredCloneHasTransferables(const uint64_t *data, size_t nbytes,
JSBool *hasTransferable);
JS_PUBLIC_API(JSBool)
JS_StructuredClone(JSContext *cx, jsval v, jsval *vp,
@ -5761,12 +5771,17 @@ class JS_PUBLIC_API(JSAutoStructuredCloneBuffer) {
bool read(JSContext *cx, jsval *vp,
const JSStructuredCloneCallbacks *optionalCallbacks=NULL,
void *closure=NULL) const;
void *closure=NULL);
bool write(JSContext *cx, jsval v,
const JSStructuredCloneCallbacks *optionalCallbacks=NULL,
void *closure=NULL);
bool write(JSContext *cx, jsval v,
jsval transferable,
const JSStructuredCloneCallbacks *optionalCallbacks=NULL,
void *closure=NULL);
/**
* Swap ownership with another JSAutoStructuredCloneBuffer.
*/
@ -5788,6 +5803,7 @@ JS_BEGIN_EXTERN_C
#define JS_SCTAG_USER_MAX ((uint32_t) 0xFFFFFFFF)
#define JS_SCERR_RECURSION 0
#define JS_SCERR_TRANSFERABLE 1
JS_PUBLIC_API(void)
JS_SetStructuredCloneCallbacks(JSRuntime *rt, const JSStructuredCloneCallbacks *callbacks);

View File

@ -18,39 +18,6 @@
using namespace js;
JS_FRIEND_API(uint64_t)
js_GetSCOffset(JSStructuredCloneWriter* writer)
{
JS_ASSERT(writer);
return writer->output().count() * sizeof(uint64_t);
}
namespace js {
bool
WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp,
const JSStructuredCloneCallbacks *cb, void *cbClosure)
{
SCOutput out(cx);
JSStructuredCloneWriter w(out, cb, cbClosure);
return w.init() && w.write(v) && out.extractBuffer(bufp, nbytesp);
}
bool
ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp,
const JSStructuredCloneCallbacks *cb, void *cbClosure)
{
SCInput in(cx, data, nbytes);
/* XXX disallow callers from using internal pointers to GC things. */
SkipRoot skip(cx, &in);
JSStructuredCloneReader r(in, cb, cbClosure);
return r.read(vp);
}
} /* namespace js */
enum StructuredDataType {
/* Structured data types provided by the engine */
SCTAG_FLOAT_MAX = 0xFFF00000,
@ -68,6 +35,8 @@ enum StructuredDataType {
SCTAG_STRING_OBJECT,
SCTAG_NUMBER_OBJECT,
SCTAG_BACK_REFERENCE_OBJECT,
SCTAG_TRANSFER_MAP_HEADER,
SCTAG_TRANSFER_MAP,
SCTAG_TYPED_ARRAY_MIN = 0xFFFF0100,
SCTAG_TYPED_ARRAY_INT8 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_INT8,
SCTAG_TYPED_ARRAY_UINT8 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_UINT8,
@ -82,6 +51,18 @@ enum StructuredDataType {
SCTAG_END_OF_BUILTIN_TYPES
};
enum TransferableMapHeader {
SCTAG_TM_NOT_MARKED = 0,
SCTAG_TM_MARKED
};
JS_FRIEND_API(uint64_t)
js_GetSCOffset(JSStructuredCloneWriter* writer)
{
JS_ASSERT(writer);
return writer->output().count() * sizeof(uint64_t);
}
static StructuredDataType
ArrayTypeToTag(uint32_t type)
{
@ -139,6 +120,81 @@ SwapBytes(uint64_t u)
#endif
}
namespace js {
bool
WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp,
const JSStructuredCloneCallbacks *cb, void *cbClosure,
jsval transferable)
{
SCOutput out(cx);
JSStructuredCloneWriter w(out, cb, cbClosure, transferable);
return w.init() && w.write(v) && out.extractBuffer(bufp, nbytesp);
}
bool
ReadStructuredClone(JSContext *cx, uint64_t *data, size_t nbytes, Value *vp,
const JSStructuredCloneCallbacks *cb, void *cbClosure)
{
SCInput in(cx, data, nbytes);
/* XXX disallow callers from using internal pointers to GC things. */
SkipRoot skip(cx, &in);
JSStructuredCloneReader r(in, cb, cbClosure);
return r.read(vp);
}
bool
ClearStructuredClone(const uint64_t *data, size_t nbytes)
{
const uint64_t *point = data;
const uint64_t *end = data + nbytes / 8;
uint64_t u = SwapBytes(*point++);
uint32_t tag = uint32_t(u >> 32);
if (tag == SCTAG_TRANSFER_MAP_HEADER) {
if ((TransferableMapHeader)uint32_t(u) == SCTAG_TM_NOT_MARKED) {
while (point != end) {
uint64_t u = SwapBytes(*point++);
uint32_t tag = uint32_t(u >> 32);
if (tag == SCTAG_TRANSFER_MAP) {
u = SwapBytes(*point++);
js_free(reinterpret_cast<void*>(u));
}
}
}
}
js_free((void *)data);
return true;
}
bool
StructuredCloneHasTransferObjects(const uint64_t *data, size_t nbytes,
bool *hasTransferable)
{
*hasTransferable = false;
if (data) {
uint64_t u = SwapBytes(*data);
uint32_t tag = uint32_t(u >> 32);
if (tag == SCTAG_TRANSFER_MAP_HEADER) {
*hasTransferable = true;
}
}
return true;
}
} /* namespace js */
static inline uint64_t
PairToUInt64(uint32_t tag, uint32_t data)
{
return uint64_t(data) | (uint64_t(tag) << 32);
}
bool
SCInput::eof()
{
@ -146,7 +202,7 @@ SCInput::eof()
return false;
}
SCInput::SCInput(JSContext *cx, const uint64_t *data, size_t nbytes)
SCInput::SCInput(JSContext *cx, uint64_t *data, size_t nbytes)
: cx(cx), point(data), end(data + nbytes / 8)
{
JS_ASSERT((uintptr_t(data) & 7) == 0);
@ -174,6 +230,42 @@ SCInput::readPair(uint32_t *tagp, uint32_t *datap)
return ok;
}
bool
SCInput::get(uint64_t *p)
{
if (point == end)
return eof();
*p = SwapBytes(*point);
return true;
}
bool
SCInput::getPair(uint32_t *tagp, uint32_t *datap)
{
uint64_t u;
if (!get(&u))
return false;
*tagp = uint32_t(u >> 32);
*datap = uint32_t(u);
return true;
}
bool
SCInput::replace(uint64_t u)
{
if (point == end)
return eof();
*point = SwapBytes(u);
return true;
}
bool
SCInput::replacePair(uint32_t tag, uint32_t data)
{
return replace(PairToUInt64(tag, data));
}
/*
* The purpose of this never-inlined function is to avoid a strange g++ build
* error on OS X 10.5 (see bug 624080). :-(
@ -236,6 +328,12 @@ SCInput::readChars(jschar *p, size_t nchars)
return readArray((uint16_t *) p, nchars);
}
bool
SCInput::readPtr(void **p)
{
return read((uint64_t *)p);
}
SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {}
bool
@ -244,12 +342,6 @@ SCOutput::write(uint64_t u)
return buf.append(SwapBytes(u));
}
static inline uint64_t
PairToUInt64(uint32_t tag, uint32_t data)
{
return uint64_t(data) | (uint64_t(tag) << 32);
}
bool
SCOutput::writePair(uint32_t tag, uint32_t data)
{
@ -344,6 +436,12 @@ SCOutput::writeChars(const jschar *p, size_t nchars)
return writeArray((const uint16_t *) p, nchars);
}
bool
SCOutput::writePtr(const void *p)
{
return write(reinterpret_cast<uint64_t>(p));
}
bool
SCOutput::extractBuffer(uint64_t **datap, size_t *sizep)
{
@ -353,6 +451,67 @@ SCOutput::extractBuffer(uint64_t **datap, size_t *sizep)
JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
bool
JSStructuredCloneWriter::parseTransferable()
{
transferableObjects.clear();
if (JSVAL_IS_NULL(transferable) || JSVAL_IS_VOID(transferable))
return true;
if (!transferable.isObject()) {
reportErrorTransferable();
return false;
}
JSObject* array = &transferable.toObject();
if (!JS_IsArrayObject(context(), array)) {
reportErrorTransferable();
return false;
}
uint32_t length;
if (!JS_GetArrayLength(context(), array, &length)) {
return false;
}
for (uint32_t i = 0; i < length; ++i) {
Value v;
if (!JS_GetElement(context(), array, i, &v)) {
return false;
}
if (!v.isObject()) {
reportErrorTransferable();
return false;
}
JSObject* tObj = &v.toObject();
if (!tObj->isArrayBuffer()) {
reportErrorTransferable();
return false;
}
// No duplicate:
if (transferableObjects.has(tObj)) {
reportErrorTransferable();
return false;
}
if (!transferableObjects.putNew(tObj))
return false;
}
return true;
}
void
JSStructuredCloneWriter::reportErrorTransferable()
{
if (callbacks && callbacks->reportError)
return callbacks->reportError(context(), JS_SCERR_TRANSFERABLE);
}
bool
JSStructuredCloneWriter::writeString(uint32_t tag, JSString *str)
{
@ -553,6 +712,32 @@ JSStructuredCloneWriter::startWrite(const Value &v)
return false;
}
bool
JSStructuredCloneWriter::writeTransferMap()
{
if (!transferableObjects.empty()) {
if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_NOT_MARKED))
return false;
for (HashSet<JSObject*>::Range r = transferableObjects.all();
!r.empty(); r.popFront()) {
JSObject *obj = r.front();
if (!memory.put(obj, memory.count()))
return false;
void *content;
if (!JS_StealArrayBufferContents(context(), obj, &content))
return false;
if (!out.writePair(SCTAG_TRANSFER_MAP, 0) || !out.writePtr(content))
return false;
}
}
return true;
}
bool
JSStructuredCloneWriter::write(const Value &v)
{
@ -849,6 +1034,20 @@ JSStructuredCloneReader::startRead(Value *vp)
return true;
}
case SCTAG_TRANSFER_MAP_HEADER:
// A map header cannot be here but just at the beginning of the buffer.
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
JSMSG_SC_BAD_SERIALIZED_DATA,
"invalid input");
return false;
case SCTAG_TRANSFER_MAP:
// A map cannot be here but just at the beginning of the buffer.
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
JSMSG_SC_BAD_SERIALIZED_DATA,
"invalid input");
return false;
case SCTAG_ARRAY_BUFFER_OBJECT:
if (!readArrayBuffer(data, vp))
return false;
@ -916,9 +1115,49 @@ JSStructuredCloneReader::readId(jsid *idp)
return false;
}
bool
JSStructuredCloneReader::readTransferMap()
{
uint32_t tag, data;
if (!in.getPair(&tag, &data))
return false;
if (tag != SCTAG_TRANSFER_MAP_HEADER ||
(TransferableMapHeader)data == SCTAG_TM_MARKED)
return true;
if (!in.replacePair(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_MARKED))
return false;
if (!in.readPair(&tag, &data))
return false;
while (1) {
if (!in.getPair(&tag, &data))
return false;
if (tag != SCTAG_TRANSFER_MAP)
break;
void *content;
if (!in.readPair(&tag, &data) || !in.readPtr(&content))
return false;
JSObject *obj = JS_NewArrayBufferWithContents(context(), content);
if (!obj || !allObjs.append(ObjectValue(*obj)))
return false;
}
return true;
}
bool
JSStructuredCloneReader::read(Value *vp)
{
if (!readTransferMap())
return false;
if (!startRead(vp))
return false;

View File

@ -16,12 +16,20 @@ namespace js {
bool
WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp,
const JSStructuredCloneCallbacks *cb, void *cbClosure);
const JSStructuredCloneCallbacks *cb, void *cbClosure,
jsval transferable);
bool
ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp,
ReadStructuredClone(JSContext *cx, uint64_t *data, size_t nbytes, Value *vp,
const JSStructuredCloneCallbacks *cb, void *cbClosure);
bool
ClearStructuredClone(const uint64_t *data, size_t nbytes);
bool
StructuredCloneHasTransferObjects(const uint64_t *data, size_t nbytes,
bool *hasTransferable);
struct SCOutput {
public:
explicit SCOutput(JSContext *cx);
@ -33,6 +41,7 @@ struct SCOutput {
bool writeDouble(double d);
bool writeBytes(const void *p, size_t nbytes);
bool writeChars(const jschar *p, size_t nchars);
bool writePtr(const void *);
template <class T>
bool writeArray(const T *p, size_t nbytes);
@ -48,7 +57,7 @@ struct SCOutput {
struct SCInput {
public:
SCInput(JSContext *cx, const uint64_t *data, size_t nbytes);
SCInput(JSContext *cx, uint64_t *data, size_t nbytes);
JSContext *context() const { return cx; }
@ -57,6 +66,13 @@ struct SCInput {
bool readDouble(double *p);
bool readBytes(void *p, size_t nbytes);
bool readChars(jschar *p, size_t nchars);
bool readPtr(void **);
bool get(uint64_t *p);
bool getPair(uint32_t *tagp, uint32_t *datap);
bool replace(uint64_t u);
bool replacePair(uint32_t tag, uint32_t data);
template <class T>
bool readArray(T *p, size_t nelems);
@ -71,8 +87,8 @@ struct SCInput {
}
JSContext *cx;
const uint64_t *point;
const uint64_t *end;
uint64_t *point;
uint64_t *end;
};
}
@ -90,6 +106,8 @@ struct JSStructuredCloneReader {
private:
JSContext *context() { return in.context(); }
bool readTransferMap();
bool checkDouble(double d);
JSString *readString(uint32_t nchars);
bool readTypedArray(uint32_t tag, uint32_t nelems, js::Value *vp);
@ -116,12 +134,17 @@ struct JSStructuredCloneReader {
struct JSStructuredCloneWriter {
public:
explicit JSStructuredCloneWriter(js::SCOutput &out, const JSStructuredCloneCallbacks *cb,
void *cbClosure)
: out(out), objs(out.context()), counts(out.context()), ids(out.context()),
memory(out.context()), callbacks(cb), closure(cbClosure) { }
explicit JSStructuredCloneWriter(js::SCOutput &out,
const JSStructuredCloneCallbacks *cb,
void *cbClosure,
jsval tVal)
: out(out), objs(out.context()),
counts(out.context()), ids(out.context()),
memory(out.context()), callbacks(cb), closure(cbClosure),
transferable(tVal), transferableObjects(out.context()) { }
bool init() { return memory.init(); }
bool init() { return transferableObjects.init() && parseTransferable() &&
memory.init() && writeTransferMap(); }
bool write(const js::Value &v);
@ -130,6 +153,8 @@ struct JSStructuredCloneWriter {
private:
JSContext *context() { return out.context(); }
bool writeTransferMap();
bool writeString(uint32_t tag, JSString *str);
bool writeId(jsid id);
bool writeArrayBuffer(JSHandleObject obj);
@ -138,6 +163,9 @@ struct JSStructuredCloneWriter {
bool startWrite(const js::Value &v);
bool traverseObject(JSHandleObject obj);
bool parseTransferable();
void reportErrorTransferable();
inline void checkStack();
js::SCOutput &out;
@ -167,6 +195,10 @@ struct JSStructuredCloneWriter {
// Any value passed to JS_WriteStructuredClone.
void *closure;
// List of transferable objects
jsval transferable;
js::HashSet<JSObject*> transferableObjects;
friend JSBool JS_WriteTypedArray(JSStructuredCloneWriter *w, jsval v);
};

View File

@ -3290,7 +3290,7 @@ Serialize(JSContext *cx, unsigned argc, jsval *vp)
jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
uint64_t *datap;
size_t nbytes;
if (!JS_WriteStructuredClone(cx, v, &datap, &nbytes, NULL, NULL))
if (!JS_WriteStructuredClone(cx, v, &datap, &nbytes, NULL, NULL, JSVAL_VOID))
return false;
JSObject *array = JS_NewUint8Array(cx, nbytes);
@ -3300,7 +3300,8 @@ Serialize(JSContext *cx, unsigned argc, jsval *vp)
}
JS_ASSERT((uintptr_t(TypedArray::viewData(array)) & 7) == 0);
js_memcpy(TypedArray::viewData(array), datap, nbytes);
JS_free(cx, datap);
JS_ClearStructuredClone(datap, nbytes);
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(array));
return true;
}