michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * This file implements the structured clone algorithm of michael@0: * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#safe-passing-of-structured-data michael@0: * michael@0: * The implementation differs slightly in that it uses an explicit stack, and michael@0: * the "memory" maps source objects to sequential integer indexes rather than michael@0: * directly pointing to destination objects. As a result, the order in which michael@0: * things are added to the memory must exactly match the order in which they michael@0: * are placed into 'allObjs', an analogous array of back-referenceable michael@0: * destination objects constructed while reading. michael@0: * michael@0: * For the most part, this is easy: simply add objects to the memory when first michael@0: * encountering them. But reading in a typed array requires an ArrayBuffer for michael@0: * construction, so objects cannot just be added to 'allObjs' in the order they michael@0: * are created. If they were, ArrayBuffers would come before typed arrays when michael@0: * in fact the typed array was added to 'memory' first. michael@0: * michael@0: * So during writing, we add objects to the memory when first encountering michael@0: * them. When reading a typed array, a placeholder is pushed onto allObjs until michael@0: * the ArrayBuffer has been read, then it is updated with the actual typed michael@0: * array object. michael@0: */ michael@0: michael@0: #include "js/StructuredClone.h" michael@0: michael@0: #include "mozilla/Endian.h" michael@0: #include "mozilla/FloatingPoint.h" michael@0: michael@0: #include michael@0: michael@0: #include "jsapi.h" michael@0: #include "jscntxt.h" michael@0: #include "jsdate.h" michael@0: #include "jswrapper.h" michael@0: michael@0: #include "vm/SharedArrayObject.h" michael@0: #include "vm/TypedArrayObject.h" michael@0: #include "vm/WrapperObject.h" michael@0: michael@0: #include "jscntxtinlines.h" michael@0: #include "jsobjinlines.h" michael@0: michael@0: using namespace js; michael@0: michael@0: using mozilla::IsNaN; michael@0: using mozilla::LittleEndian; michael@0: using mozilla::NativeEndian; michael@0: using JS::CanonicalizeNaN; michael@0: michael@0: enum StructuredDataType { michael@0: /* Structured data types provided by the engine */ michael@0: SCTAG_FLOAT_MAX = 0xFFF00000, michael@0: SCTAG_NULL = 0xFFFF0000, michael@0: SCTAG_UNDEFINED, michael@0: SCTAG_BOOLEAN, michael@0: SCTAG_INDEX, michael@0: SCTAG_STRING, michael@0: SCTAG_DATE_OBJECT, michael@0: SCTAG_REGEXP_OBJECT, michael@0: SCTAG_ARRAY_OBJECT, michael@0: SCTAG_OBJECT_OBJECT, michael@0: SCTAG_ARRAY_BUFFER_OBJECT, michael@0: SCTAG_BOOLEAN_OBJECT, michael@0: SCTAG_STRING_OBJECT, michael@0: SCTAG_NUMBER_OBJECT, michael@0: SCTAG_BACK_REFERENCE_OBJECT, michael@0: SCTAG_DO_NOT_USE_1, // Required for backwards compatibility michael@0: SCTAG_DO_NOT_USE_2, // Required for backwards compatibility michael@0: SCTAG_TYPED_ARRAY_OBJECT, michael@0: SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100, michael@0: SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_INT8, michael@0: SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_UINT8, michael@0: SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_INT16, michael@0: SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_UINT16, michael@0: SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_INT32, michael@0: SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_UINT32, michael@0: SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_FLOAT32, michael@0: SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_FLOAT64, michael@0: SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_UINT8_CLAMPED, michael@0: SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_MAX - 1, michael@0: michael@0: /* michael@0: * Define a separate range of numbers for Transferable-only tags, since michael@0: * they are not used for persistent clone buffers and therefore do not michael@0: * require bumping JS_STRUCTURED_CLONE_VERSION. michael@0: */ michael@0: SCTAG_TRANSFER_MAP_HEADER = 0xFFFF0200, michael@0: SCTAG_TRANSFER_MAP_PENDING_ENTRY, michael@0: SCTAG_TRANSFER_MAP_ARRAY_BUFFER, michael@0: SCTAG_TRANSFER_MAP_SHARED_BUFFER, michael@0: SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES, michael@0: michael@0: SCTAG_END_OF_BUILTIN_TYPES michael@0: }; michael@0: michael@0: /* michael@0: * Format of transfer map: michael@0: * michael@0: * numTransferables (64 bits) michael@0: * array of: michael@0: * michael@0: * pointer (64 bits) michael@0: * extraData (64 bits), eg byte length for ArrayBuffers michael@0: */ michael@0: michael@0: // Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the michael@0: // contents have been read out yet or not. michael@0: enum TransferableMapHeader { michael@0: SCTAG_TM_UNREAD = 0, michael@0: SCTAG_TM_TRANSFERRED michael@0: }; michael@0: michael@0: static inline uint64_t michael@0: PairToUInt64(uint32_t tag, uint32_t data) michael@0: { michael@0: return uint64_t(data) | (uint64_t(tag) << 32); michael@0: } michael@0: michael@0: namespace js { michael@0: michael@0: struct SCOutput { michael@0: public: michael@0: explicit SCOutput(JSContext *cx); michael@0: michael@0: JSContext *context() const { return cx; } michael@0: michael@0: bool write(uint64_t u); michael@0: bool writePair(uint32_t tag, uint32_t data); michael@0: bool writeDouble(double d); michael@0: bool writeBytes(const void *p, size_t nbytes); michael@0: bool writeChars(const jschar *p, size_t nchars); michael@0: bool writePtr(const void *); michael@0: michael@0: template michael@0: bool writeArray(const T *p, size_t nbytes); michael@0: michael@0: bool extractBuffer(uint64_t **datap, size_t *sizep); michael@0: michael@0: uint64_t count() const { return buf.length(); } michael@0: uint64_t *rawBuffer() { return buf.begin(); } michael@0: michael@0: private: michael@0: JSContext *cx; michael@0: Vector buf; michael@0: }; michael@0: michael@0: class SCInput { michael@0: public: michael@0: SCInput(JSContext *cx, uint64_t *data, size_t nbytes); michael@0: michael@0: JSContext *context() const { return cx; } michael@0: michael@0: static void getPtr(const uint64_t *buffer, void **ptr); michael@0: static void getPair(const uint64_t *buffer, uint32_t *tagp, uint32_t *datap); michael@0: michael@0: bool read(uint64_t *p); michael@0: bool readNativeEndian(uint64_t *p); michael@0: bool readPair(uint32_t *tagp, uint32_t *datap); michael@0: bool readDouble(double *p); michael@0: bool readBytes(void *p, size_t nbytes); michael@0: bool readChars(jschar *p, size_t nchars); michael@0: bool readPtr(void **); michael@0: michael@0: bool get(uint64_t *p); michael@0: bool getPair(uint32_t *tagp, uint32_t *datap); michael@0: michael@0: uint64_t *tell() const { return point; } michael@0: uint64_t *end() const { return bufEnd; } michael@0: michael@0: template michael@0: bool readArray(T *p, size_t nelems); michael@0: michael@0: bool reportTruncated() { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_SC_BAD_SERIALIZED_DATA, "truncated"); michael@0: return false; michael@0: } michael@0: michael@0: private: michael@0: void staticAssertions() { michael@0: JS_STATIC_ASSERT(sizeof(jschar) == 2); michael@0: JS_STATIC_ASSERT(sizeof(uint32_t) == 4); michael@0: JS_STATIC_ASSERT(sizeof(double) == 8); michael@0: } michael@0: michael@0: JSContext *cx; michael@0: uint64_t *point; michael@0: uint64_t *bufEnd; michael@0: }; michael@0: michael@0: } /* namespace js */ michael@0: michael@0: struct JSStructuredCloneReader { michael@0: public: michael@0: explicit JSStructuredCloneReader(SCInput &in, const JSStructuredCloneCallbacks *cb, michael@0: void *cbClosure) michael@0: : in(in), objs(in.context()), allObjs(in.context()), michael@0: callbacks(cb), closure(cbClosure) { } michael@0: michael@0: SCInput &input() { return in; } michael@0: bool read(Value *vp); michael@0: michael@0: private: michael@0: JSContext *context() { return in.context(); } michael@0: michael@0: bool readTransferMap(); michael@0: michael@0: bool checkDouble(double d); michael@0: JSString *readString(uint32_t nchars); michael@0: bool readTypedArray(uint32_t arrayType, uint32_t nelems, Value *vp, bool v1Read = false); michael@0: bool readArrayBuffer(uint32_t nbytes, Value *vp); michael@0: bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, Value *vp); michael@0: bool readId(jsid *idp); michael@0: bool startRead(Value *vp); michael@0: michael@0: SCInput ∈ michael@0: michael@0: // Stack of objects with properties remaining to be read. michael@0: AutoValueVector objs; michael@0: michael@0: // Stack of all objects read during this deserialization michael@0: AutoValueVector allObjs; michael@0: michael@0: // The user defined callbacks that will be used for cloning. michael@0: const JSStructuredCloneCallbacks *callbacks; michael@0: michael@0: // Any value passed to JS_ReadStructuredClone. michael@0: void *closure; michael@0: michael@0: friend bool JS_ReadTypedArray(JSStructuredCloneReader *r, MutableHandleValue vp); michael@0: }; michael@0: michael@0: struct JSStructuredCloneWriter { michael@0: public: michael@0: explicit JSStructuredCloneWriter(JSContext *cx, michael@0: const JSStructuredCloneCallbacks *cb, michael@0: void *cbClosure, michael@0: jsval tVal) michael@0: : out(cx), objs(out.context()), michael@0: counts(out.context()), ids(out.context()), michael@0: memory(out.context()), callbacks(cb), closure(cbClosure), michael@0: transferable(out.context(), tVal), transferableObjects(out.context()) { } michael@0: michael@0: ~JSStructuredCloneWriter(); michael@0: michael@0: bool init() { return memory.init() && parseTransferable() && writeTransferMap(); } michael@0: michael@0: bool write(const Value &v); michael@0: michael@0: SCOutput &output() { return out; } michael@0: michael@0: bool extractBuffer(uint64_t **datap, size_t *sizep) { michael@0: return out.extractBuffer(datap, sizep); michael@0: } michael@0: michael@0: private: michael@0: JSContext *context() { return out.context(); } michael@0: michael@0: bool writeTransferMap(); michael@0: michael@0: bool writeString(uint32_t tag, JSString *str); michael@0: bool writeId(jsid id); michael@0: bool writeArrayBuffer(HandleObject obj); michael@0: bool writeTypedArray(HandleObject obj); michael@0: bool startObject(HandleObject obj, bool *backref); michael@0: bool startWrite(const Value &v); michael@0: bool traverseObject(HandleObject obj); michael@0: michael@0: bool parseTransferable(); michael@0: bool reportErrorTransferable(); michael@0: bool transferOwnership(); michael@0: michael@0: inline void checkStack(); michael@0: michael@0: SCOutput out; michael@0: michael@0: // Vector of objects with properties remaining to be written. michael@0: // michael@0: // NB: These can span multiple compartments, so the compartment must be michael@0: // entered before any manipulation is performed. michael@0: AutoValueVector objs; michael@0: michael@0: // counts[i] is the number of properties of objs[i] remaining to be written. michael@0: // counts.length() == objs.length() and sum(counts) == ids.length(). michael@0: Vector counts; michael@0: michael@0: // Ids of properties remaining to be written. michael@0: AutoIdVector ids; michael@0: michael@0: // The "memory" list described in the HTML5 internal structured cloning algorithm. michael@0: // memory is a superset of objs; items are never removed from Memory michael@0: // until a serialization operation is finished michael@0: typedef AutoObjectUnsigned32HashMap CloneMemory; michael@0: CloneMemory memory; michael@0: michael@0: // The user defined callbacks that will be used for cloning. michael@0: const JSStructuredCloneCallbacks *callbacks; michael@0: michael@0: // Any value passed to JS_WriteStructuredClone. michael@0: void *closure; michael@0: michael@0: // List of transferable objects michael@0: RootedValue transferable; michael@0: AutoObjectVector transferableObjects; michael@0: michael@0: friend bool JS_WriteTypedArray(JSStructuredCloneWriter *w, HandleValue v); michael@0: }; michael@0: michael@0: JS_FRIEND_API(uint64_t) michael@0: js_GetSCOffset(JSStructuredCloneWriter* writer) michael@0: { michael@0: JS_ASSERT(writer); michael@0: return writer->output().count() * sizeof(uint64_t); michael@0: } michael@0: michael@0: JS_STATIC_ASSERT(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN); michael@0: JS_STATIC_ASSERT(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX); michael@0: JS_STATIC_ASSERT(ScalarTypeDescr::TYPE_INT8 == 0); michael@0: michael@0: static void michael@0: ReportErrorTransferable(JSContext *cx, const JSStructuredCloneCallbacks *callbacks) michael@0: { michael@0: if (callbacks && callbacks->reportError) michael@0: callbacks->reportError(cx, JS_SCERR_TRANSFERABLE); michael@0: else michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_SC_NOT_TRANSFERABLE); michael@0: } michael@0: michael@0: bool michael@0: WriteStructuredClone(JSContext *cx, HandleValue v, uint64_t **bufp, size_t *nbytesp, michael@0: const JSStructuredCloneCallbacks *cb, void *cbClosure, michael@0: jsval transferable) michael@0: { michael@0: JSStructuredCloneWriter w(cx, cb, cbClosure, transferable); michael@0: return w.init() && w.write(v) && w.extractBuffer(bufp, nbytesp); michael@0: } michael@0: michael@0: bool michael@0: ReadStructuredClone(JSContext *cx, uint64_t *data, size_t nbytes, MutableHandleValue vp, michael@0: const JSStructuredCloneCallbacks *cb, void *cbClosure) michael@0: { michael@0: SCInput in(cx, data, nbytes); michael@0: JSStructuredCloneReader r(in, cb, cbClosure); michael@0: return r.read(vp.address()); michael@0: } michael@0: michael@0: // If the given buffer contains Transferables, free them. Note that custom michael@0: // Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to michael@0: // delete their transferables. michael@0: static void michael@0: Discard(uint64_t *buffer, size_t nbytes, const JSStructuredCloneCallbacks *cb, void *cbClosure) michael@0: { michael@0: JS_ASSERT(nbytes % sizeof(uint64_t) == 0); michael@0: if (nbytes < sizeof(uint64_t)) michael@0: return; // Empty buffer michael@0: michael@0: uint64_t *point = buffer; michael@0: uint32_t tag, data; michael@0: SCInput::getPair(point++, &tag, &data); michael@0: if (tag != SCTAG_TRANSFER_MAP_HEADER) michael@0: return; michael@0: michael@0: if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) michael@0: return; michael@0: michael@0: // freeTransfer should not GC michael@0: JS::AutoAssertNoGC nogc; michael@0: michael@0: uint64_t numTransferables = LittleEndian::readUint64(point++); michael@0: while (numTransferables--) { michael@0: uint32_t ownership; michael@0: SCInput::getPair(point++, &tag, &ownership); michael@0: JS_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY); michael@0: michael@0: void *content; michael@0: SCInput::getPtr(point++, &content); michael@0: michael@0: uint64_t extraData = LittleEndian::readUint64(point++); michael@0: michael@0: if (ownership < JS::SCTAG_TMO_FIRST_OWNED) michael@0: continue; michael@0: michael@0: if (ownership == JS::SCTAG_TMO_ALLOC_DATA) { michael@0: js_free(content); michael@0: } else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) { michael@0: JS_ReleaseMappedArrayBufferContents(content, extraData); michael@0: } else if (ownership == JS::SCTAG_TMO_SHARED_BUFFER) { michael@0: SharedArrayRawBuffer *raw = static_cast(content); michael@0: if (raw) michael@0: raw->dropReference(); michael@0: } else if (cb && cb->freeTransfer) { michael@0: cb->freeTransfer(tag, JS::TransferableOwnership(ownership), content, extraData, cbClosure); michael@0: } else { michael@0: MOZ_ASSERT(false, "unknown ownership"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void michael@0: ClearStructuredClone(uint64_t *data, size_t nbytes, michael@0: const JSStructuredCloneCallbacks *cb, void *cbClosure) michael@0: { michael@0: Discard(data, nbytes, cb, cbClosure); michael@0: js_free(data); michael@0: } michael@0: michael@0: bool michael@0: StructuredCloneHasTransferObjects(const uint64_t *data, size_t nbytes, bool *hasTransferable) michael@0: { michael@0: *hasTransferable = false; michael@0: michael@0: if (data) { michael@0: uint64_t u = LittleEndian::readUint64(data); michael@0: uint32_t tag = uint32_t(u >> 32); michael@0: if (tag == SCTAG_TRANSFER_MAP_HEADER) michael@0: *hasTransferable = true; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: namespace js { michael@0: michael@0: SCInput::SCInput(JSContext *cx, uint64_t *data, size_t nbytes) michael@0: : cx(cx), point(data), bufEnd(data + nbytes / 8) michael@0: { michael@0: // On 32-bit, we sometimes construct an SCInput from an SCOutput buffer, michael@0: // which is not guaranteed to be 8-byte aligned michael@0: JS_ASSERT((uintptr_t(data) & (sizeof(int) - 1)) == 0); michael@0: JS_ASSERT((nbytes & 7) == 0); michael@0: } michael@0: michael@0: bool michael@0: SCInput::read(uint64_t *p) michael@0: { michael@0: if (point == bufEnd) { michael@0: *p = 0; /* initialize to shut GCC up */ michael@0: return reportTruncated(); michael@0: } michael@0: *p = LittleEndian::readUint64(point++); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: SCInput::readNativeEndian(uint64_t *p) michael@0: { michael@0: if (point == bufEnd) { michael@0: *p = 0; /* initialize to shut GCC up */ michael@0: return reportTruncated(); michael@0: } michael@0: *p = *(point++); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: SCInput::readPair(uint32_t *tagp, uint32_t *datap) michael@0: { michael@0: uint64_t u; michael@0: bool ok = read(&u); michael@0: if (ok) { michael@0: *tagp = uint32_t(u >> 32); michael@0: *datap = uint32_t(u); michael@0: } michael@0: return ok; michael@0: } michael@0: michael@0: bool michael@0: SCInput::get(uint64_t *p) michael@0: { michael@0: if (point == bufEnd) michael@0: return reportTruncated(); michael@0: *p = LittleEndian::readUint64(point); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: SCInput::getPair(uint32_t *tagp, uint32_t *datap) michael@0: { michael@0: uint64_t u = 0; michael@0: if (!get(&u)) michael@0: return false; michael@0: michael@0: *tagp = uint32_t(u >> 32); michael@0: *datap = uint32_t(u); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: SCInput::getPair(const uint64_t *p, uint32_t *tagp, uint32_t *datap) michael@0: { michael@0: uint64_t u = LittleEndian::readUint64(p); michael@0: *tagp = uint32_t(u >> 32); michael@0: *datap = uint32_t(u); michael@0: } michael@0: michael@0: bool michael@0: SCInput::readDouble(double *p) michael@0: { michael@0: union { michael@0: uint64_t u; michael@0: double d; michael@0: } pun; michael@0: if (!read(&pun.u)) michael@0: return false; michael@0: *p = CanonicalizeNaN(pun.d); michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: static void michael@0: copyAndSwapFromLittleEndian(T *dest, const void *src, size_t nelems) michael@0: { michael@0: if (nelems > 0) michael@0: NativeEndian::copyAndSwapFromLittleEndian(dest, src, nelems); michael@0: } michael@0: michael@0: template <> michael@0: void michael@0: copyAndSwapFromLittleEndian(uint8_t *dest, const void *src, size_t nelems) michael@0: { michael@0: memcpy(dest, src, nelems); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: SCInput::readArray(T *p, size_t nelems) michael@0: { michael@0: JS_STATIC_ASSERT(sizeof(uint64_t) % sizeof(T) == 0); michael@0: michael@0: /* michael@0: * Fail if nelems is so huge as to make JS_HOWMANY overflow or if nwords is michael@0: * larger than the remaining data. michael@0: */ michael@0: size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T)); michael@0: if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems || nwords > size_t(bufEnd - point)) michael@0: return reportTruncated(); michael@0: michael@0: copyAndSwapFromLittleEndian(p, point, nelems); michael@0: point += nwords; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: SCInput::readBytes(void *p, size_t nbytes) michael@0: { michael@0: return readArray((uint8_t *) p, nbytes); michael@0: } michael@0: michael@0: bool michael@0: SCInput::readChars(jschar *p, size_t nchars) michael@0: { michael@0: JS_ASSERT(sizeof(jschar) == sizeof(uint16_t)); michael@0: return readArray((uint16_t *) p, nchars); michael@0: } michael@0: michael@0: void michael@0: SCInput::getPtr(const uint64_t *p, void **ptr) michael@0: { michael@0: // No endianness conversion is used for pointers, since they are not sent michael@0: // across address spaces anyway. michael@0: *ptr = reinterpret_cast(*p); michael@0: } michael@0: michael@0: bool michael@0: SCInput::readPtr(void **p) michael@0: { michael@0: uint64_t u; michael@0: if (!readNativeEndian(&u)) michael@0: return false; michael@0: *p = reinterpret_cast(u); michael@0: return true; michael@0: } michael@0: michael@0: SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {} michael@0: michael@0: bool michael@0: SCOutput::write(uint64_t u) michael@0: { michael@0: return buf.append(NativeEndian::swapToLittleEndian(u)); michael@0: } michael@0: michael@0: bool michael@0: SCOutput::writePair(uint32_t tag, uint32_t data) michael@0: { michael@0: /* michael@0: * As it happens, the tag word appears after the data word in the output. michael@0: * This is because exponents occupy the last 2 bytes of doubles on the michael@0: * little-endian platforms we care most about. michael@0: * michael@0: * For example, JSVAL_TRUE is written using writePair(SCTAG_BOOLEAN, 1). michael@0: * PairToUInt64 produces the number 0xFFFF000200000001. michael@0: * That is written out as the bytes 01 00 00 00 02 00 FF FF. michael@0: */ michael@0: return write(PairToUInt64(tag, data)); michael@0: } michael@0: michael@0: static inline uint64_t michael@0: ReinterpretDoubleAsUInt64(double d) michael@0: { michael@0: union { michael@0: double d; michael@0: uint64_t u; michael@0: } pun; michael@0: pun.d = d; michael@0: return pun.u; michael@0: } michael@0: michael@0: static inline double michael@0: ReinterpretUInt64AsDouble(uint64_t u) michael@0: { michael@0: union { michael@0: uint64_t u; michael@0: double d; michael@0: } pun; michael@0: pun.u = u; michael@0: return pun.d; michael@0: } michael@0: michael@0: static inline double michael@0: ReinterpretPairAsDouble(uint32_t tag, uint32_t data) michael@0: { michael@0: return ReinterpretUInt64AsDouble(PairToUInt64(tag, data)); michael@0: } michael@0: michael@0: bool michael@0: SCOutput::writeDouble(double d) michael@0: { michael@0: return write(ReinterpretDoubleAsUInt64(CanonicalizeNaN(d))); michael@0: } michael@0: michael@0: template michael@0: static void michael@0: copyAndSwapToLittleEndian(void *dest, const T *src, size_t nelems) michael@0: { michael@0: if (nelems > 0) michael@0: NativeEndian::copyAndSwapToLittleEndian(dest, src, nelems); michael@0: } michael@0: michael@0: template <> michael@0: void michael@0: copyAndSwapToLittleEndian(void *dest, const uint8_t *src, size_t nelems) michael@0: { michael@0: memcpy(dest, src, nelems); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: SCOutput::writeArray(const T *p, size_t nelems) michael@0: { michael@0: JS_ASSERT(8 % sizeof(T) == 0); michael@0: JS_ASSERT(sizeof(uint64_t) % sizeof(T) == 0); michael@0: michael@0: if (nelems == 0) michael@0: return true; michael@0: michael@0: if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems) { michael@0: js_ReportAllocationOverflow(context()); michael@0: return false; michael@0: } michael@0: size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T)); michael@0: size_t start = buf.length(); michael@0: if (!buf.growByUninitialized(nwords)) michael@0: return false; michael@0: michael@0: buf.back() = 0; /* zero-pad to an 8-byte boundary */ michael@0: michael@0: T *q = (T *) &buf[start]; michael@0: copyAndSwapToLittleEndian(q, p, nelems); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: SCOutput::writeBytes(const void *p, size_t nbytes) michael@0: { michael@0: return writeArray((const uint8_t *) p, nbytes); michael@0: } michael@0: michael@0: bool michael@0: SCOutput::writeChars(const jschar *p, size_t nchars) michael@0: { michael@0: JS_ASSERT(sizeof(jschar) == sizeof(uint16_t)); michael@0: return writeArray((const uint16_t *) p, nchars); michael@0: } michael@0: michael@0: bool michael@0: SCOutput::writePtr(const void *p) michael@0: { michael@0: return write(reinterpret_cast(p)); michael@0: } michael@0: michael@0: bool michael@0: SCOutput::extractBuffer(uint64_t **datap, size_t *sizep) michael@0: { michael@0: *sizep = buf.length() * sizeof(uint64_t); michael@0: return (*datap = buf.extractRawBuffer()) != nullptr; michael@0: } michael@0: michael@0: } /* namespace js */ michael@0: michael@0: JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX); michael@0: michael@0: JSStructuredCloneWriter::~JSStructuredCloneWriter() michael@0: { michael@0: // Free any transferable data left lying around in the buffer michael@0: uint64_t *data; michael@0: size_t size; michael@0: MOZ_ALWAYS_TRUE(extractBuffer(&data, &size)); michael@0: ClearStructuredClone(data, size, callbacks, closure); michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneWriter::parseTransferable() michael@0: { michael@0: MOZ_ASSERT(transferableObjects.empty(), "parseTransferable called with stale data"); michael@0: michael@0: if (JSVAL_IS_NULL(transferable) || JSVAL_IS_VOID(transferable)) michael@0: return true; michael@0: michael@0: if (!transferable.isObject()) michael@0: return reportErrorTransferable(); michael@0: michael@0: JSContext *cx = context(); michael@0: RootedObject array(cx, &transferable.toObject()); michael@0: if (!JS_IsArrayObject(cx, array)) michael@0: return reportErrorTransferable(); michael@0: michael@0: uint32_t length; michael@0: if (!JS_GetArrayLength(cx, array, &length)) { michael@0: return false; michael@0: } michael@0: michael@0: RootedValue v(context()); michael@0: michael@0: for (uint32_t i = 0; i < length; ++i) { michael@0: if (!JS_GetElement(cx, array, i, &v)) michael@0: return false; michael@0: michael@0: if (!v.isObject()) michael@0: return reportErrorTransferable(); michael@0: michael@0: RootedObject tObj(context(), CheckedUnwrap(&v.toObject())); michael@0: michael@0: if (!tObj) { michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); michael@0: return false; michael@0: } michael@0: michael@0: // No duplicates allowed michael@0: if (std::find(transferableObjects.begin(), transferableObjects.end(), tObj) != transferableObjects.end()) { michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_SC_DUP_TRANSFERABLE); michael@0: return false; michael@0: } michael@0: michael@0: if (!transferableObjects.append(tObj)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneWriter::reportErrorTransferable() michael@0: { michael@0: ReportErrorTransferable(context(), callbacks); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneWriter::writeString(uint32_t tag, JSString *str) michael@0: { michael@0: size_t length = str->length(); michael@0: const jschar *chars = str->getChars(context()); michael@0: if (!chars) michael@0: return false; michael@0: return out.writePair(tag, uint32_t(length)) && out.writeChars(chars, length); michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneWriter::writeId(jsid id) michael@0: { michael@0: if (JSID_IS_INT(id)) michael@0: return out.writePair(SCTAG_INDEX, uint32_t(JSID_TO_INT(id))); michael@0: JS_ASSERT(JSID_IS_STRING(id)); michael@0: return writeString(SCTAG_STRING, JSID_TO_STRING(id)); michael@0: } michael@0: michael@0: inline void michael@0: JSStructuredCloneWriter::checkStack() michael@0: { michael@0: #ifdef DEBUG michael@0: /* To avoid making serialization O(n^2), limit stack-checking at 10. */ michael@0: const size_t MAX = 10; michael@0: michael@0: size_t limit = Min(counts.length(), MAX); michael@0: JS_ASSERT(objs.length() == counts.length()); michael@0: size_t total = 0; michael@0: for (size_t i = 0; i < limit; i++) { michael@0: JS_ASSERT(total + counts[i] >= total); michael@0: total += counts[i]; michael@0: } michael@0: if (counts.length() <= MAX) michael@0: JS_ASSERT(total == ids.length()); michael@0: else michael@0: JS_ASSERT(total <= ids.length()); michael@0: michael@0: size_t j = objs.length(); michael@0: for (size_t i = 0; i < limit; i++) michael@0: JS_ASSERT(memory.has(&objs[--j].toObject())); michael@0: #endif michael@0: } michael@0: michael@0: /* michael@0: * Write out a typed array. Note that post-v1 structured clone buffers do not michael@0: * perform endianness conversion on stored data, so multibyte typed arrays michael@0: * cannot be deserialized into a different endianness machine. Endianness michael@0: * conversion would prevent sharing ArrayBuffers: if you have Int8Array and michael@0: * Int16Array views of the same ArrayBuffer, should the data bytes be michael@0: * byte-swapped when writing or not? The Int8Array requires them to not be michael@0: * swapped; the Int16Array requires that they are. michael@0: */ michael@0: bool michael@0: JSStructuredCloneWriter::writeTypedArray(HandleObject obj) michael@0: { michael@0: Rooted tarr(context(), &obj->as()); michael@0: michael@0: if (!TypedArrayObject::ensureHasBuffer(context(), tarr)) michael@0: return false; michael@0: michael@0: if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, tarr->length())) michael@0: return false; michael@0: uint64_t type = tarr->type(); michael@0: if (!out.write(type)) michael@0: return false; michael@0: michael@0: // Write out the ArrayBuffer tag and contents michael@0: RootedValue val(context(), TypedArrayObject::bufferValue(tarr)); michael@0: if (!startWrite(val)) michael@0: return false; michael@0: michael@0: return out.write(tarr->byteOffset()); michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj) michael@0: { michael@0: ArrayBufferObject &buffer = obj->as(); michael@0: michael@0: return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, buffer.byteLength()) && michael@0: out.writeBytes(buffer.dataPointer(), buffer.byteLength()); michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneWriter::startObject(HandleObject obj, bool *backref) michael@0: { michael@0: /* Handle cycles in the object graph. */ michael@0: CloneMemory::AddPtr p = memory.lookupForAdd(obj); michael@0: if ((*backref = p)) michael@0: return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value()); michael@0: if (!memory.add(p, obj, memory.count())) michael@0: return false; michael@0: michael@0: if (memory.count() == UINT32_MAX) { michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, michael@0: JSMSG_NEED_DIET, "object graph to serialize"); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneWriter::traverseObject(HandleObject obj) michael@0: { michael@0: /* michael@0: * Get enumerable property ids and put them in reverse order so that they michael@0: * will come off the stack in forward order. michael@0: */ michael@0: size_t initialLength = ids.length(); michael@0: if (!GetPropertyNames(context(), obj, JSITER_OWNONLY, &ids)) michael@0: return false; michael@0: jsid *begin = ids.begin() + initialLength, *end = ids.end(); michael@0: size_t count = size_t(end - begin); michael@0: Reverse(begin, end); michael@0: michael@0: /* Push obj and count to the stack. */ michael@0: if (!objs.append(ObjectValue(*obj)) || !counts.append(count)) michael@0: return false; michael@0: checkStack(); michael@0: michael@0: /* Write the header for obj. */ michael@0: return out.writePair(obj->is() ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0); michael@0: } michael@0: michael@0: static bool michael@0: PrimitiveToObject(JSContext *cx, Value *vp) michael@0: { michael@0: JSObject *obj = PrimitiveToObject(cx, *vp); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: vp->setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneWriter::startWrite(const Value &v) michael@0: { michael@0: assertSameCompartment(context(), v); michael@0: michael@0: if (v.isString()) { michael@0: return writeString(SCTAG_STRING, v.toString()); michael@0: } else if (v.isNumber()) { michael@0: return out.writeDouble(v.toNumber()); michael@0: } else if (v.isBoolean()) { michael@0: return out.writePair(SCTAG_BOOLEAN, v.toBoolean()); michael@0: } else if (v.isNull()) { michael@0: return out.writePair(SCTAG_NULL, 0); michael@0: } else if (v.isUndefined()) { michael@0: return out.writePair(SCTAG_UNDEFINED, 0); michael@0: } else if (v.isObject()) { michael@0: RootedObject obj(context(), &v.toObject()); michael@0: michael@0: // The object might be a security wrapper. See if we can clone what's michael@0: // behind it. If we can, unwrap the object. michael@0: obj = CheckedUnwrap(obj); michael@0: if (!obj) { michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); michael@0: return false; michael@0: } michael@0: michael@0: AutoCompartment ac(context(), obj); michael@0: michael@0: bool backref; michael@0: if (!startObject(obj, &backref)) michael@0: return false; michael@0: if (backref) michael@0: return true; michael@0: michael@0: if (obj->is()) { michael@0: RegExpObject &reobj = obj->as(); michael@0: return out.writePair(SCTAG_REGEXP_OBJECT, reobj.getFlags()) && michael@0: writeString(SCTAG_STRING, reobj.getSource()); michael@0: } else if (obj->is()) { michael@0: double d = js_DateGetMsecSinceEpoch(obj); michael@0: return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(d); michael@0: } else if (obj->is()) { michael@0: return writeTypedArray(obj); michael@0: } else if (obj->is() && obj->as().hasData()) { michael@0: return writeArrayBuffer(obj); michael@0: } else if (obj->is() || obj->is()) { michael@0: return traverseObject(obj); michael@0: } else if (obj->is()) { michael@0: return out.writePair(SCTAG_BOOLEAN_OBJECT, obj->as().unbox()); michael@0: } else if (obj->is()) { michael@0: return out.writePair(SCTAG_NUMBER_OBJECT, 0) && michael@0: out.writeDouble(obj->as().unbox()); michael@0: } else if (obj->is()) { michael@0: return writeString(SCTAG_STRING_OBJECT, obj->as().unbox()); michael@0: } michael@0: michael@0: if (callbacks && callbacks->write) michael@0: return callbacks->write(context(), this, obj, closure); michael@0: /* else fall through */ michael@0: } michael@0: michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_SC_UNSUPPORTED_TYPE); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneWriter::writeTransferMap() michael@0: { michael@0: if (transferableObjects.empty()) michael@0: return true; michael@0: michael@0: if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD)) michael@0: return false; michael@0: michael@0: if (!out.write(transferableObjects.length())) michael@0: return false; michael@0: michael@0: for (JS::AutoObjectVector::Range tr = transferableObjects.all(); !tr.empty(); tr.popFront()) { michael@0: JSObject *obj = tr.front(); michael@0: michael@0: if (!memory.put(obj, memory.count())) michael@0: return false; michael@0: michael@0: // Emit a placeholder pointer. We will steal the data and neuter the michael@0: // transferable later, in the case of ArrayBufferObject. michael@0: if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY, JS::SCTAG_TMO_UNFILLED)) michael@0: return false; michael@0: if (!out.writePtr(nullptr)) // Pointer to ArrayBuffer contents or to SharedArrayRawBuffer. michael@0: return false; michael@0: if (!out.write(0)) // extraData michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneWriter::transferOwnership() michael@0: { michael@0: if (transferableObjects.empty()) michael@0: return true; michael@0: michael@0: // Walk along the transferables and the transfer map at the same time, michael@0: // grabbing out pointers from the transferables and stuffing them into the michael@0: // transfer map. michael@0: uint64_t *point = out.rawBuffer(); michael@0: JS_ASSERT(uint32_t(LittleEndian::readUint64(point) >> 32) == SCTAG_TRANSFER_MAP_HEADER); michael@0: point++; michael@0: JS_ASSERT(LittleEndian::readUint64(point) == transferableObjects.length()); michael@0: point++; michael@0: michael@0: for (JS::AutoObjectVector::Range tr = transferableObjects.all(); !tr.empty(); tr.popFront()) { michael@0: RootedObject obj(context(), tr.front()); michael@0: michael@0: uint32_t tag; michael@0: JS::TransferableOwnership ownership; michael@0: void *content; michael@0: uint64_t extraData; michael@0: michael@0: #if DEBUG michael@0: SCInput::getPair(point, &tag, (uint32_t*) &ownership); michael@0: MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY); michael@0: MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED); michael@0: #endif michael@0: michael@0: if (obj->is()) { michael@0: size_t nbytes = obj->as().byteLength(); michael@0: content = JS_StealArrayBufferContents(context(), obj); michael@0: if (!content) michael@0: return false; // Destructor will clean up the already-transferred data michael@0: tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER; michael@0: if (obj->as().isMappedArrayBuffer()) michael@0: ownership = JS::SCTAG_TMO_MAPPED_DATA; michael@0: else michael@0: ownership = JS::SCTAG_TMO_ALLOC_DATA; michael@0: extraData = nbytes; michael@0: } else if (obj->is()) { michael@0: SharedArrayRawBuffer *rawbuf = obj->as().rawBufferObject(); michael@0: michael@0: // Avoids a race condition where the parent thread frees the buffer michael@0: // before the child has accepted the transferable. michael@0: rawbuf->addReference(); michael@0: michael@0: tag = SCTAG_TRANSFER_MAP_SHARED_BUFFER; michael@0: ownership = JS::SCTAG_TMO_SHARED_BUFFER; michael@0: content = rawbuf; michael@0: extraData = 0; michael@0: } else { michael@0: if (!callbacks || !callbacks->writeTransfer) michael@0: return reportErrorTransferable(); michael@0: if (!callbacks->writeTransfer(context(), obj, closure, &tag, &ownership, &content, &extraData)) michael@0: return false; michael@0: JS_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY); michael@0: } michael@0: michael@0: LittleEndian::writeUint64(point++, PairToUInt64(tag, ownership)); michael@0: LittleEndian::writeUint64(point++, reinterpret_cast(content)); michael@0: LittleEndian::writeUint64(point++, extraData); michael@0: } michael@0: michael@0: JS_ASSERT(point <= out.rawBuffer() + out.count()); michael@0: JS_ASSERT_IF(point < out.rawBuffer() + out.count(), michael@0: uint32_t(LittleEndian::readUint64(point) >> 32) < SCTAG_TRANSFER_MAP_HEADER); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneWriter::write(const Value &v) michael@0: { michael@0: if (!startWrite(v)) michael@0: return false; michael@0: michael@0: while (!counts.empty()) { michael@0: RootedObject obj(context(), &objs.back().toObject()); michael@0: AutoCompartment ac(context(), obj); michael@0: if (counts.back()) { michael@0: counts.back()--; michael@0: RootedId id(context(), ids.back()); michael@0: ids.popBack(); michael@0: checkStack(); michael@0: if (JSID_IS_STRING(id) || JSID_IS_INT(id)) { michael@0: /* michael@0: * If obj still has an own property named id, write it out. michael@0: * The cost of re-checking could be avoided by using michael@0: * NativeIterators. michael@0: */ michael@0: bool found; michael@0: if (!HasOwnProperty(context(), obj, id, &found)) michael@0: return false; michael@0: michael@0: if (found) { michael@0: RootedValue val(context()); michael@0: if (!writeId(id) || michael@0: !JSObject::getGeneric(context(), obj, obj, id, &val) || michael@0: !startWrite(val)) michael@0: return false; michael@0: } michael@0: } michael@0: } else { michael@0: out.writePair(SCTAG_NULL, 0); michael@0: objs.popBack(); michael@0: counts.popBack(); michael@0: } michael@0: } michael@0: michael@0: memory.clear(); michael@0: return transferOwnership(); michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneReader::checkDouble(double d) michael@0: { michael@0: jsval_layout l; michael@0: l.asDouble = d; michael@0: if (!JSVAL_IS_DOUBLE_IMPL(l)) { michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, michael@0: JSMSG_SC_BAD_SERIALIZED_DATA, "unrecognized NaN"); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class Chars { michael@0: JSContext *cx; michael@0: jschar *p; michael@0: public: michael@0: Chars(JSContext *cx) : cx(cx), p(nullptr) {} michael@0: ~Chars() { js_free(p); } michael@0: michael@0: bool allocate(size_t len) { michael@0: JS_ASSERT(!p); michael@0: // We're going to null-terminate! michael@0: p = cx->pod_malloc(len + 1); michael@0: if (p) { michael@0: p[len] = jschar(0); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: jschar *get() { return p; } michael@0: void forget() { p = nullptr; } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: JSString * michael@0: JSStructuredCloneReader::readString(uint32_t nchars) michael@0: { michael@0: if (nchars > JSString::MAX_LENGTH) { michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, michael@0: JSMSG_SC_BAD_SERIALIZED_DATA, "string length"); michael@0: return nullptr; michael@0: } michael@0: Chars chars(context()); michael@0: if (!chars.allocate(nchars) || !in.readChars(chars.get(), nchars)) michael@0: return nullptr; michael@0: JSString *str = js_NewString(context(), chars.get(), nchars); michael@0: if (str) michael@0: chars.forget(); michael@0: return str; michael@0: } michael@0: michael@0: static uint32_t michael@0: TagToV1ArrayType(uint32_t tag) michael@0: { michael@0: JS_ASSERT(tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX); michael@0: return tag - SCTAG_TYPED_ARRAY_V1_MIN; michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneReader::readTypedArray(uint32_t arrayType, uint32_t nelems, Value *vp, michael@0: bool v1Read) michael@0: { michael@0: if (arrayType > ScalarTypeDescr::TYPE_UINT8_CLAMPED) { michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, michael@0: JSMSG_SC_BAD_SERIALIZED_DATA, "unhandled typed array element type"); michael@0: return false; michael@0: } michael@0: michael@0: // Push a placeholder onto the allObjs list to stand in for the typed array michael@0: uint32_t placeholderIndex = allObjs.length(); michael@0: Value dummy = JSVAL_NULL; michael@0: if (!allObjs.append(dummy)) michael@0: return false; michael@0: michael@0: // Read the ArrayBuffer object and its contents (but no properties) michael@0: RootedValue v(context()); michael@0: uint32_t byteOffset; michael@0: if (v1Read) { michael@0: if (!readV1ArrayBuffer(arrayType, nelems, v.address())) michael@0: return false; michael@0: byteOffset = 0; michael@0: } else { michael@0: if (!startRead(v.address())) michael@0: return false; michael@0: uint64_t n; michael@0: if (!in.read(&n)) michael@0: return false; michael@0: byteOffset = n; michael@0: } michael@0: RootedObject buffer(context(), &v.toObject()); michael@0: RootedObject obj(context(), nullptr); michael@0: michael@0: switch (arrayType) { michael@0: case ScalarTypeDescr::TYPE_INT8: michael@0: obj = JS_NewInt8ArrayWithBuffer(context(), buffer, byteOffset, nelems); michael@0: break; michael@0: case ScalarTypeDescr::TYPE_UINT8: michael@0: obj = JS_NewUint8ArrayWithBuffer(context(), buffer, byteOffset, nelems); michael@0: break; michael@0: case ScalarTypeDescr::TYPE_INT16: michael@0: obj = JS_NewInt16ArrayWithBuffer(context(), buffer, byteOffset, nelems); michael@0: break; michael@0: case ScalarTypeDescr::TYPE_UINT16: michael@0: obj = JS_NewUint16ArrayWithBuffer(context(), buffer, byteOffset, nelems); michael@0: break; michael@0: case ScalarTypeDescr::TYPE_INT32: michael@0: obj = JS_NewInt32ArrayWithBuffer(context(), buffer, byteOffset, nelems); michael@0: break; michael@0: case ScalarTypeDescr::TYPE_UINT32: michael@0: obj = JS_NewUint32ArrayWithBuffer(context(), buffer, byteOffset, nelems); michael@0: break; michael@0: case ScalarTypeDescr::TYPE_FLOAT32: michael@0: obj = JS_NewFloat32ArrayWithBuffer(context(), buffer, byteOffset, nelems); michael@0: break; michael@0: case ScalarTypeDescr::TYPE_FLOAT64: michael@0: obj = JS_NewFloat64ArrayWithBuffer(context(), buffer, byteOffset, nelems); michael@0: break; michael@0: case ScalarTypeDescr::TYPE_UINT8_CLAMPED: michael@0: obj = JS_NewUint8ClampedArrayWithBuffer(context(), buffer, byteOffset, nelems); michael@0: break; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("unknown TypedArrayObject type"); michael@0: } michael@0: michael@0: if (!obj) michael@0: return false; michael@0: vp->setObject(*obj); michael@0: michael@0: allObjs[placeholderIndex] = *vp; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, Value *vp) michael@0: { michael@0: JSObject *obj = ArrayBufferObject::create(context(), nbytes); michael@0: if (!obj) michael@0: return false; michael@0: vp->setObject(*obj); michael@0: ArrayBufferObject &buffer = obj->as(); michael@0: JS_ASSERT(buffer.byteLength() == nbytes); michael@0: return in.readArray(buffer.dataPointer(), nbytes); michael@0: } michael@0: michael@0: static size_t michael@0: bytesPerTypedArrayElement(uint32_t arrayType) michael@0: { michael@0: switch (arrayType) { michael@0: case ScalarTypeDescr::TYPE_INT8: michael@0: case ScalarTypeDescr::TYPE_UINT8: michael@0: case ScalarTypeDescr::TYPE_UINT8_CLAMPED: michael@0: return sizeof(uint8_t); michael@0: case ScalarTypeDescr::TYPE_INT16: michael@0: case ScalarTypeDescr::TYPE_UINT16: michael@0: return sizeof(uint16_t); michael@0: case ScalarTypeDescr::TYPE_INT32: michael@0: case ScalarTypeDescr::TYPE_UINT32: michael@0: case ScalarTypeDescr::TYPE_FLOAT32: michael@0: return sizeof(uint32_t); michael@0: case ScalarTypeDescr::TYPE_FLOAT64: michael@0: return sizeof(uint64_t); michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("unknown TypedArrayObject type"); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Read in the data for a structured clone version 1 ArrayBuffer, performing michael@0: * endianness-conversion while reading. michael@0: */ michael@0: bool michael@0: JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, Value *vp) michael@0: { michael@0: JS_ASSERT(arrayType <= ScalarTypeDescr::TYPE_UINT8_CLAMPED); michael@0: michael@0: uint32_t nbytes = nelems * bytesPerTypedArrayElement(arrayType); michael@0: JSObject *obj = ArrayBufferObject::create(context(), nbytes); michael@0: if (!obj) michael@0: return false; michael@0: vp->setObject(*obj); michael@0: ArrayBufferObject &buffer = obj->as(); michael@0: JS_ASSERT(buffer.byteLength() == nbytes); michael@0: michael@0: switch (arrayType) { michael@0: case ScalarTypeDescr::TYPE_INT8: michael@0: case ScalarTypeDescr::TYPE_UINT8: michael@0: case ScalarTypeDescr::TYPE_UINT8_CLAMPED: michael@0: return in.readArray((uint8_t*) buffer.dataPointer(), nelems); michael@0: case ScalarTypeDescr::TYPE_INT16: michael@0: case ScalarTypeDescr::TYPE_UINT16: michael@0: return in.readArray((uint16_t*) buffer.dataPointer(), nelems); michael@0: case ScalarTypeDescr::TYPE_INT32: michael@0: case ScalarTypeDescr::TYPE_UINT32: michael@0: case ScalarTypeDescr::TYPE_FLOAT32: michael@0: return in.readArray((uint32_t*) buffer.dataPointer(), nelems); michael@0: case ScalarTypeDescr::TYPE_FLOAT64: michael@0: return in.readArray((uint64_t*) buffer.dataPointer(), nelems); michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("unknown TypedArrayObject type"); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneReader::startRead(Value *vp) michael@0: { michael@0: uint32_t tag, data; michael@0: michael@0: if (!in.readPair(&tag, &data)) michael@0: return false; michael@0: switch (tag) { michael@0: case SCTAG_NULL: michael@0: vp->setNull(); michael@0: break; michael@0: michael@0: case SCTAG_UNDEFINED: michael@0: vp->setUndefined(); michael@0: break; michael@0: michael@0: case SCTAG_BOOLEAN: michael@0: case SCTAG_BOOLEAN_OBJECT: michael@0: vp->setBoolean(!!data); michael@0: if (tag == SCTAG_BOOLEAN_OBJECT && !PrimitiveToObject(context(), vp)) michael@0: return false; michael@0: break; michael@0: michael@0: case SCTAG_STRING: michael@0: case SCTAG_STRING_OBJECT: { michael@0: JSString *str = readString(data); michael@0: if (!str) michael@0: return false; michael@0: vp->setString(str); michael@0: if (tag == SCTAG_STRING_OBJECT && !PrimitiveToObject(context(), vp)) michael@0: return false; michael@0: break; michael@0: } michael@0: michael@0: case SCTAG_NUMBER_OBJECT: { michael@0: double d; michael@0: if (!in.readDouble(&d) || !checkDouble(d)) michael@0: return false; michael@0: vp->setDouble(d); michael@0: if (!PrimitiveToObject(context(), vp)) michael@0: return false; michael@0: break; michael@0: } michael@0: michael@0: case SCTAG_DATE_OBJECT: { michael@0: double d; michael@0: if (!in.readDouble(&d) || !checkDouble(d)) michael@0: return false; michael@0: if (!IsNaN(d) && d != TimeClip(d)) { michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, michael@0: JSMSG_SC_BAD_SERIALIZED_DATA, "date"); michael@0: return false; michael@0: } michael@0: JSObject *obj = js_NewDateObjectMsec(context(), d); michael@0: if (!obj) michael@0: return false; michael@0: vp->setObject(*obj); michael@0: break; michael@0: } michael@0: michael@0: case SCTAG_REGEXP_OBJECT: { michael@0: RegExpFlag flags = RegExpFlag(data); michael@0: uint32_t tag2, nchars; michael@0: if (!in.readPair(&tag2, &nchars)) michael@0: return false; michael@0: if (tag2 != SCTAG_STRING) { michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, michael@0: JSMSG_SC_BAD_SERIALIZED_DATA, "regexp"); michael@0: return false; michael@0: } michael@0: JSString *str = readString(nchars); michael@0: if (!str) michael@0: return false; michael@0: JSFlatString *flat = str->ensureFlat(context()); michael@0: if (!flat) michael@0: return false; michael@0: michael@0: RegExpObject *reobj = RegExpObject::createNoStatics(context(), flat->chars(), michael@0: flat->length(), flags, nullptr); michael@0: if (!reobj) michael@0: return false; michael@0: vp->setObject(*reobj); michael@0: break; michael@0: } michael@0: michael@0: case SCTAG_ARRAY_OBJECT: michael@0: case SCTAG_OBJECT_OBJECT: { michael@0: JSObject *obj = (tag == SCTAG_ARRAY_OBJECT) michael@0: ? NewDenseEmptyArray(context()) michael@0: : NewBuiltinClassInstance(context(), &JSObject::class_); michael@0: if (!obj || !objs.append(ObjectValue(*obj))) michael@0: return false; michael@0: vp->setObject(*obj); michael@0: break; michael@0: } michael@0: michael@0: case SCTAG_BACK_REFERENCE_OBJECT: { michael@0: if (data >= allObjs.length()) { michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, michael@0: JSMSG_SC_BAD_SERIALIZED_DATA, michael@0: "invalid back reference in input"); michael@0: return false; michael@0: } michael@0: *vp = allObjs[data]; michael@0: return true; michael@0: } michael@0: michael@0: case SCTAG_TRANSFER_MAP_HEADER: michael@0: case SCTAG_TRANSFER_MAP_PENDING_ENTRY: michael@0: // We should be past all the transfer map tags. michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, michael@0: JSMSG_SC_BAD_SERIALIZED_DATA, michael@0: "invalid input"); michael@0: return false; michael@0: michael@0: case SCTAG_ARRAY_BUFFER_OBJECT: michael@0: if (!readArrayBuffer(data, vp)) michael@0: return false; michael@0: break; michael@0: michael@0: case SCTAG_TYPED_ARRAY_OBJECT: michael@0: // readTypedArray adds the array to allObjs michael@0: uint64_t arrayType; michael@0: if (!in.read(&arrayType)) michael@0: return false; michael@0: return readTypedArray(arrayType, data, vp); michael@0: break; michael@0: michael@0: default: { michael@0: if (tag <= SCTAG_FLOAT_MAX) { michael@0: double d = ReinterpretPairAsDouble(tag, data); michael@0: if (!checkDouble(d)) michael@0: return false; michael@0: vp->setNumber(d); michael@0: break; michael@0: } michael@0: michael@0: if (SCTAG_TYPED_ARRAY_V1_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_V1_MAX) { michael@0: // A v1-format typed array michael@0: // readTypedArray adds the array to allObjs michael@0: return readTypedArray(TagToV1ArrayType(tag), data, vp, true); michael@0: } michael@0: michael@0: if (!callbacks || !callbacks->read) { michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, michael@0: JSMSG_SC_BAD_SERIALIZED_DATA, "unsupported type"); michael@0: return false; michael@0: } michael@0: JSObject *obj = callbacks->read(context(), this, tag, data, closure); michael@0: if (!obj) michael@0: return false; michael@0: vp->setObject(*obj); michael@0: } michael@0: } michael@0: michael@0: if (vp->isObject() && !allObjs.append(*vp)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneReader::readId(jsid *idp) michael@0: { michael@0: uint32_t tag, data; michael@0: if (!in.readPair(&tag, &data)) michael@0: return false; michael@0: michael@0: if (tag == SCTAG_INDEX) { michael@0: *idp = INT_TO_JSID(int32_t(data)); michael@0: return true; michael@0: } michael@0: if (tag == SCTAG_STRING) { michael@0: JSString *str = readString(data); michael@0: if (!str) michael@0: return false; michael@0: JSAtom *atom = AtomizeString(context(), str); michael@0: if (!atom) michael@0: return false; michael@0: *idp = NON_INTEGER_ATOM_TO_JSID(atom); michael@0: return true; michael@0: } michael@0: if (tag == SCTAG_NULL) { michael@0: *idp = JSID_VOID; michael@0: return true; michael@0: } michael@0: JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, michael@0: JSMSG_SC_BAD_SERIALIZED_DATA, "id"); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneReader::readTransferMap() michael@0: { michael@0: JSContext *cx = context(); michael@0: uint64_t *headerPos = in.tell(); michael@0: michael@0: uint32_t tag, data; michael@0: if (!in.getPair(&tag, &data)) michael@0: return in.reportTruncated(); michael@0: michael@0: if (tag != SCTAG_TRANSFER_MAP_HEADER || TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) michael@0: return true; michael@0: michael@0: uint64_t numTransferables; michael@0: MOZ_ALWAYS_TRUE(in.readPair(&tag, &data)); michael@0: if (!in.read(&numTransferables)) michael@0: return false; michael@0: michael@0: for (uint64_t i = 0; i < numTransferables; i++) { michael@0: uint64_t *pos = in.tell(); michael@0: michael@0: if (!in.readPair(&tag, &data)) michael@0: return false; michael@0: michael@0: JS_ASSERT(tag != SCTAG_TRANSFER_MAP_PENDING_ENTRY); michael@0: RootedObject obj(cx); michael@0: michael@0: void *content; michael@0: if (!in.readPtr(&content)) michael@0: return false; michael@0: michael@0: uint64_t extraData; michael@0: if (!in.read(&extraData)) michael@0: return false; michael@0: michael@0: if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) { michael@0: size_t nbytes = extraData; michael@0: JS_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA || michael@0: data == JS::SCTAG_TMO_MAPPED_DATA); michael@0: if (data == JS::SCTAG_TMO_ALLOC_DATA) michael@0: obj = JS_NewArrayBufferWithContents(cx, nbytes, content); michael@0: else if (data == JS::SCTAG_TMO_MAPPED_DATA) michael@0: obj = JS_NewMappedArrayBufferWithContents(cx, nbytes, content); michael@0: } else if (tag == SCTAG_TRANSFER_MAP_SHARED_BUFFER) { michael@0: JS_ASSERT(data == JS::SCTAG_TMO_SHARED_BUFFER); michael@0: obj = SharedArrayBufferObject::New(context(), (SharedArrayRawBuffer *)content); michael@0: } else { michael@0: if (!callbacks || !callbacks->readTransfer) { michael@0: ReportErrorTransferable(cx, callbacks); michael@0: return false; michael@0: } michael@0: if (!callbacks->readTransfer(cx, this, tag, content, extraData, closure, &obj)) michael@0: return false; michael@0: MOZ_ASSERT(obj); michael@0: MOZ_ASSERT(!cx->isExceptionPending()); michael@0: } michael@0: michael@0: // On failure, the buffer will still own the data (since its ownership will not get set to SCTAG_TMO_UNOWNED), michael@0: // so the data will be freed by ClearStructuredClone michael@0: if (!obj) michael@0: return false; michael@0: michael@0: // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input michael@0: // buffer. michael@0: *pos = PairToUInt64(tag, JS::SCTAG_TMO_UNOWNED); michael@0: MOZ_ASSERT(headerPos < pos && pos < in.end()); michael@0: michael@0: if (!allObjs.append(ObjectValue(*obj))) michael@0: return false; michael@0: } michael@0: michael@0: // Mark the whole transfer map as consumed. michael@0: MOZ_ASSERT(headerPos <= in.tell()); michael@0: #ifdef DEBUG michael@0: SCInput::getPair(headerPos, &tag, &data); michael@0: MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER); michael@0: MOZ_ASSERT(TransferableMapHeader(data) != SCTAG_TM_TRANSFERRED); michael@0: #endif michael@0: *headerPos = PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JSStructuredCloneReader::read(Value *vp) michael@0: { michael@0: if (!readTransferMap()) michael@0: return false; michael@0: michael@0: if (!startRead(vp)) michael@0: return false; michael@0: michael@0: while (objs.length() != 0) { michael@0: RootedObject obj(context(), &objs.back().toObject()); michael@0: michael@0: RootedId id(context()); michael@0: if (!readId(id.address())) michael@0: return false; michael@0: michael@0: if (JSID_IS_VOID(id)) { michael@0: objs.popBack(); michael@0: } else { michael@0: RootedValue v(context()); michael@0: if (!startRead(v.address()) || !JSObject::defineGeneric(context(), obj, id, v)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: allObjs.clear(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: using namespace js; michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_ReadStructuredClone(JSContext *cx, uint64_t *buf, size_t nbytes, michael@0: uint32_t version, MutableHandleValue vp, michael@0: const JSStructuredCloneCallbacks *optionalCallbacks, michael@0: void *closure) michael@0: { michael@0: AssertHeapIsIdle(cx); michael@0: CHECK_REQUEST(cx); michael@0: michael@0: if (version > JS_STRUCTURED_CLONE_VERSION) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_CLONE_VERSION); michael@0: return false; michael@0: } michael@0: const JSStructuredCloneCallbacks *callbacks = michael@0: optionalCallbacks ? michael@0: optionalCallbacks : michael@0: cx->runtime()->structuredCloneCallbacks; michael@0: return ReadStructuredClone(cx, buf, nbytes, vp, callbacks, closure); michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_WriteStructuredClone(JSContext *cx, HandleValue value, uint64_t **bufp, size_t *nbytesp, michael@0: const JSStructuredCloneCallbacks *optionalCallbacks, michael@0: void *closure, HandleValue transferable) michael@0: { michael@0: AssertHeapIsIdle(cx); michael@0: CHECK_REQUEST(cx); michael@0: assertSameCompartment(cx, value); michael@0: michael@0: const JSStructuredCloneCallbacks *callbacks = michael@0: optionalCallbacks ? michael@0: optionalCallbacks : michael@0: cx->runtime()->structuredCloneCallbacks; michael@0: return WriteStructuredClone(cx, value, bufp, nbytesp, callbacks, closure, transferable); michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_ClearStructuredClone(uint64_t *data, size_t nbytes, michael@0: const JSStructuredCloneCallbacks *optionalCallbacks, michael@0: void *closure) michael@0: { michael@0: ClearStructuredClone(data, nbytes, optionalCallbacks, closure); michael@0: return true; michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_StructuredCloneHasTransferables(const uint64_t *data, size_t nbytes, michael@0: bool *hasTransferable) michael@0: { michael@0: bool transferable; michael@0: if (!StructuredCloneHasTransferObjects(data, nbytes, &transferable)) michael@0: return false; michael@0: michael@0: *hasTransferable = transferable; michael@0: return true; michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_StructuredClone(JSContext *cx, HandleValue value, MutableHandleValue vp, michael@0: const JSStructuredCloneCallbacks *optionalCallbacks, michael@0: void *closure) michael@0: { michael@0: AssertHeapIsIdle(cx); michael@0: CHECK_REQUEST(cx); michael@0: michael@0: // Strings are associated with zones, not compartments, michael@0: // so we copy the string by wrapping it. michael@0: if (value.isString()) { michael@0: RootedString strValue(cx, value.toString()); michael@0: if (!cx->compartment()->wrap(cx, strValue.address())) { michael@0: return false; michael@0: } michael@0: vp.setString(strValue); michael@0: return true; michael@0: } michael@0: michael@0: const JSStructuredCloneCallbacks *callbacks = michael@0: optionalCallbacks ? michael@0: optionalCallbacks : michael@0: cx->runtime()->structuredCloneCallbacks; michael@0: michael@0: JSAutoStructuredCloneBuffer buf; michael@0: { michael@0: // If we use Maybe here, G++ can't tell that the michael@0: // destructor is only called when Maybe::construct was called, and michael@0: // we get warnings about using uninitialized variables. michael@0: if (value.isObject()) { michael@0: AutoCompartment ac(cx, &value.toObject()); michael@0: if (!buf.write(cx, value, callbacks, closure)) michael@0: return false; michael@0: } else { michael@0: if (!buf.write(cx, value, callbacks, closure)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return buf.read(cx, vp, callbacks, closure); michael@0: } michael@0: michael@0: JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer &&other) michael@0: { michael@0: other.steal(&data_, &nbytes_, &version_); michael@0: } michael@0: michael@0: JSAutoStructuredCloneBuffer& michael@0: JSAutoStructuredCloneBuffer::operator=(JSAutoStructuredCloneBuffer &&other) michael@0: { michael@0: JS_ASSERT(&other != this); michael@0: clear(); michael@0: other.steal(&data_, &nbytes_, &version_); michael@0: return *this; michael@0: } michael@0: michael@0: void michael@0: JSAutoStructuredCloneBuffer::clear() michael@0: { michael@0: if (data_) { michael@0: ClearStructuredClone(data_, nbytes_, callbacks_, closure_); michael@0: data_ = nullptr; michael@0: nbytes_ = 0; michael@0: version_ = 0; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: JSAutoStructuredCloneBuffer::copy(const uint64_t *srcData, size_t nbytes, uint32_t version) michael@0: { michael@0: // transferable objects cannot be copied michael@0: bool hasTransferable; michael@0: if (!StructuredCloneHasTransferObjects(data_, nbytes_, &hasTransferable) || michael@0: hasTransferable) michael@0: return false; michael@0: michael@0: uint64_t *newData = static_cast(js_malloc(nbytes)); michael@0: if (!newData) michael@0: return false; michael@0: michael@0: js_memcpy(newData, srcData, nbytes); michael@0: michael@0: clear(); michael@0: data_ = newData; michael@0: nbytes_ = nbytes; michael@0: version_ = version; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: JSAutoStructuredCloneBuffer::adopt(uint64_t *data, size_t nbytes, uint32_t version) michael@0: { michael@0: clear(); michael@0: data_ = data; michael@0: nbytes_ = nbytes; michael@0: version_ = version; michael@0: } michael@0: michael@0: void michael@0: JSAutoStructuredCloneBuffer::steal(uint64_t **datap, size_t *nbytesp, uint32_t *versionp) michael@0: { michael@0: *datap = data_; michael@0: *nbytesp = nbytes_; michael@0: if (versionp) michael@0: *versionp = version_; michael@0: michael@0: data_ = nullptr; michael@0: nbytes_ = 0; michael@0: version_ = 0; michael@0: } michael@0: michael@0: bool michael@0: JSAutoStructuredCloneBuffer::read(JSContext *cx, MutableHandleValue vp, michael@0: const JSStructuredCloneCallbacks *optionalCallbacks, michael@0: void *closure) michael@0: { michael@0: JS_ASSERT(cx); michael@0: JS_ASSERT(data_); michael@0: return !!JS_ReadStructuredClone(cx, data_, nbytes_, version_, vp, michael@0: optionalCallbacks, closure); michael@0: } michael@0: michael@0: bool michael@0: JSAutoStructuredCloneBuffer::write(JSContext *cx, HandleValue value, michael@0: const JSStructuredCloneCallbacks *optionalCallbacks, michael@0: void *closure) michael@0: { michael@0: HandleValue transferable = UndefinedHandleValue; michael@0: return write(cx, value, transferable, optionalCallbacks, closure); michael@0: } michael@0: michael@0: bool michael@0: JSAutoStructuredCloneBuffer::write(JSContext *cx, HandleValue value, michael@0: HandleValue transferable, michael@0: const JSStructuredCloneCallbacks *optionalCallbacks, michael@0: void *closure) michael@0: { michael@0: clear(); michael@0: bool ok = !!JS_WriteStructuredClone(cx, value, &data_, &nbytes_, michael@0: optionalCallbacks, closure, michael@0: transferable); michael@0: if (!ok) { michael@0: data_ = nullptr; michael@0: nbytes_ = 0; michael@0: version_ = JS_STRUCTURED_CLONE_VERSION; michael@0: } michael@0: return ok; michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_SetStructuredCloneCallbacks(JSRuntime *rt, const JSStructuredCloneCallbacks *callbacks) michael@0: { michael@0: rt->structuredCloneCallbacks = callbacks; michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_ReadUint32Pair(JSStructuredCloneReader *r, uint32_t *p1, uint32_t *p2) michael@0: { michael@0: return r->input().readPair((uint32_t *) p1, (uint32_t *) p2); michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_ReadBytes(JSStructuredCloneReader *r, void *p, size_t len) michael@0: { michael@0: return r->input().readBytes(p, len); michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_ReadTypedArray(JSStructuredCloneReader *r, MutableHandleValue vp) michael@0: { michael@0: uint32_t tag, nelems; michael@0: if (!r->input().readPair(&tag, &nelems)) michael@0: return false; michael@0: if (tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX) { michael@0: return r->readTypedArray(TagToV1ArrayType(tag), nelems, vp.address(), true); michael@0: } else if (tag == SCTAG_TYPED_ARRAY_OBJECT) { michael@0: uint64_t arrayType; michael@0: if (!r->input().read(&arrayType)) michael@0: return false; michael@0: return r->readTypedArray(arrayType, nelems, vp.address()); michael@0: } else { michael@0: JS_ReportErrorNumber(r->context(), js_GetErrorMessage, nullptr, michael@0: JSMSG_SC_BAD_SERIALIZED_DATA, "expected type array"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_WriteUint32Pair(JSStructuredCloneWriter *w, uint32_t tag, uint32_t data) michael@0: { michael@0: return w->output().writePair(tag, data); michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_WriteBytes(JSStructuredCloneWriter *w, const void *p, size_t len) michael@0: { michael@0: return w->output().writeBytes(p, len); michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS_WriteTypedArray(JSStructuredCloneWriter *w, HandleValue v) michael@0: { michael@0: JS_ASSERT(v.isObject()); michael@0: assertSameCompartment(w->context(), v); michael@0: RootedObject obj(w->context(), &v.toObject()); michael@0: michael@0: // If the object is a security wrapper, see if we're allowed to unwrap it. michael@0: // If we aren't, throw. michael@0: if (obj->is()) michael@0: obj = CheckedUnwrap(obj); michael@0: if (!obj) { michael@0: JS_ReportErrorNumber(w->context(), js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); michael@0: return false; michael@0: } michael@0: return w->writeTypedArray(obj); michael@0: }