mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 595297 - Portable serialize/deserialize implementation of the HTML5 structured cloning algorithm (jsclone.cpp). r=gal.
This commit is contained in:
parent
b4b683166b
commit
00704fa52d
@ -121,6 +121,7 @@ CPPSRCS = \
|
||||
jsarray.cpp \
|
||||
jsatom.cpp \
|
||||
jsbool.cpp \
|
||||
jsclone.cpp \
|
||||
jscntxt.cpp \
|
||||
jsdate.cpp \
|
||||
jsdbgapi.cpp \
|
||||
@ -177,6 +178,7 @@ INSTALLED_HEADERS = \
|
||||
jsbit.h \
|
||||
jsbool.h \
|
||||
jsclist.h \
|
||||
jsclone.h \
|
||||
jscntxt.h \
|
||||
jscompat.h \
|
||||
jsdate.h \
|
||||
|
@ -339,3 +339,6 @@ MSG_DEF(JSMSG_CALLER_IS_STRICT, 256, 0, JSEXN_TYPEERR, "access to strict m
|
||||
MSG_DEF(JSMSG_NEED_DEBUG_MODE, 257, 0, JSEXN_ERR, "function can be called only in debug mode")
|
||||
MSG_DEF(JSMSG_STRICT_CODE_LET_EXPR_STMT, 258, 0, JSEXN_ERR, "strict mode code may not contain unparenthesized let expression statements")
|
||||
MSG_DEF(JSMSG_CANT_CHANGE_EXTENSIBILITY, 259, 0, JSEXN_TYPEERR, "can't change object's extensibility")
|
||||
MSG_DEF(JSMSG_SC_BAD_SERIALIZED_DATA, 260, 1, JSEXN_INTERNALERR, "bad serialized structured data ({0})")
|
||||
MSG_DEF(JSMSG_SC_UNSUPPORTED_TYPE, 261, 0, JSEXN_TYPEERR, "unsupported type for structured data")
|
||||
MSG_DEF(JSMSG_SC_RECURSION, 262, 0, JSEXN_INTERNALERR, "recursive object")
|
||||
|
@ -57,6 +57,7 @@
|
||||
#include "jsatom.h"
|
||||
#include "jsbool.h"
|
||||
#include "jsbuiltins.h"
|
||||
#include "jsclone.h"
|
||||
#include "jscntxt.h"
|
||||
#include "jsversion.h"
|
||||
#include "jsdate.h"
|
||||
@ -5245,6 +5246,55 @@ JS_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver)
|
||||
return js_FinishJSONParse(cx, jp, Valueify(reviver));
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_ReadStructuredClone(JSContext *cx, const uint64 *buf, size_t nbytes, jsval *vp)
|
||||
{
|
||||
return ReadStructuredClone(cx, buf, nbytes, Valueify(vp));
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_WriteStructuredClone(JSContext *cx, jsval v, uint64 **bufp, size_t *nbytesp)
|
||||
{
|
||||
return WriteStructuredClone(cx, Valueify(v), (uint64_t **) bufp, nbytesp);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_StructuredClone(JSContext *cx, jsval v, jsval *vp)
|
||||
{
|
||||
JSAutoStructuredCloneBuffer buf(cx);
|
||||
return buf.write(v) && buf.read(vp);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS_SetStructuredCloneCallbacks(JSRuntime *rt, const JSStructuredCloneCallbacks *callbacks)
|
||||
{
|
||||
rt->structuredCloneCallbacks = callbacks;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_ReadPair(JSStructuredCloneReader *r, uint32 *p1, uint32 *p2)
|
||||
{
|
||||
return r->input().readPair((uint32_t *) p1, (uint32_t *) p2);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_ReadBytes(JSStructuredCloneReader *r, void *p, size_t len)
|
||||
{
|
||||
return r->input().readBytes(p, len);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_WritePair(JSStructuredCloneWriter *w, uint32 tag, uint32 data)
|
||||
{
|
||||
return w->output().writePair(tag, data);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_WriteBytes(JSStructuredCloneWriter *w, const void *p, size_t len)
|
||||
{
|
||||
return w->output().writeBytes(p, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* The following determines whether C Strings are to be treated as UTF-8
|
||||
* or ISO-8859-1. For correct operation, it must be set prior to the
|
||||
|
106
js/src/jsapi.h
106
js/src/jsapi.h
@ -2844,6 +2844,112 @@ JS_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver);
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
/* API for the HTML5 internal structured cloning algorithm. */
|
||||
|
||||
/* The maximum supported structured-clone serialization format version. */
|
||||
#define JS_STRUCTURED_CLONE_VERSION 1
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_ReadStructuredClone(JSContext *cx, const uint64 *data, size_t nbytes, jsval *vp);
|
||||
|
||||
/* Note: On success, the caller is responsible for calling js_free(*datap). */
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_WriteStructuredClone(JSContext *cx, jsval v, uint64 **datap, size_t *nbytesp);
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_StructuredClone(JSContext *cx, jsval v, jsval *vp);
|
||||
|
||||
#ifdef __cplusplus
|
||||
/* RAII sugar for JS_WriteStructuredClone. */
|
||||
class JSAutoStructuredCloneBuffer {
|
||||
JSContext *cx;
|
||||
uint64 *data_;
|
||||
size_t nbytes_;
|
||||
|
||||
public:
|
||||
explicit JSAutoStructuredCloneBuffer(JSContext *cx) : cx(cx), data_(NULL), nbytes_(0) {}
|
||||
~JSAutoStructuredCloneBuffer() { clear(); }
|
||||
|
||||
uint64 *data() const { return data_; }
|
||||
size_t nbytes() const { return nbytes_; }
|
||||
|
||||
void clear() {
|
||||
if (data_) {
|
||||
JS_free(cx, data_);
|
||||
data_ = NULL;
|
||||
nbytes_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Adopt some memory. It will be automatically freed by the destructor.
|
||||
* data must have been allocated using JS_malloc.
|
||||
*/
|
||||
void adopt(uint64 *data, size_t nbytes) {
|
||||
clear();
|
||||
data_ = data;
|
||||
nbytes_ = nbytes;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the buffer so that it will not be automatically freed.
|
||||
* After this, the caller is responsible for calling JS_free(*datap).
|
||||
*/
|
||||
void steal(uint64 **datap, size_t *nbytesp) {
|
||||
*datap = data_;
|
||||
*nbytesp = nbytes_;
|
||||
data_ = NULL;
|
||||
nbytes_ = 0;
|
||||
}
|
||||
|
||||
bool read(jsval *vp) const {
|
||||
JS_ASSERT(data_);
|
||||
return !!JS_ReadStructuredClone(cx, data_, nbytes_, vp);
|
||||
}
|
||||
|
||||
bool write(jsval v) {
|
||||
clear();
|
||||
bool ok = !!JS_WriteStructuredClone(cx, v, &data_, &nbytes_);
|
||||
if (!ok) {
|
||||
data_ = NULL;
|
||||
nbytes_ = 0;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* API for implementing custom serialization behavior (for ImageData, File, etc.) */
|
||||
|
||||
/* The range of tag values the application may use for its own custom object types. */
|
||||
#define JS_SCTAG_USER_MIN ((uint32) 0xFFFF8000)
|
||||
#define JS_SCTAG_USER_MAX ((uint32) 0xFFFFFFFF)
|
||||
|
||||
#define JS_SCERR_RECURSION 0
|
||||
|
||||
struct JSStructuredCloneCallbacks {
|
||||
ReadStructuredCloneOp read;
|
||||
WriteStructuredCloneOp write;
|
||||
StructuredCloneErrorOp reportError;
|
||||
};
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS_SetStructuredCloneCallbacks(JSRuntime *rt, const JSStructuredCloneCallbacks *callbacks);
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_ReadPair(JSStructuredCloneReader *r, uint32 *p1, uint32 *p2);
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_ReadBytes(JSStructuredCloneReader *r, void *p, size_t len);
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_WritePair(JSStructuredCloneWriter *w, uint32 tag, uint32 data);
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_WriteBytes(JSStructuredCloneWriter *w, const void *p, size_t len);
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
/*
|
||||
* Locale specific string conversion and error message callbacks.
|
||||
*/
|
||||
|
@ -2683,7 +2683,7 @@ array_indexOfHelper(JSContext *cx, JSBool isLast, uintN argc, Value *vp)
|
||||
return JS_FALSE;
|
||||
}
|
||||
if (!hole && StrictlyEqual(cx, *vp, tosearch)) {
|
||||
vp->setNumber(i);
|
||||
vp->setNumber(i);
|
||||
return JS_TRUE;
|
||||
}
|
||||
if (i == stop)
|
||||
|
815
js/src/jsclone.cpp
Normal file
815
js/src/jsclone.cpp
Normal file
@ -0,0 +1,815 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is JavaScript structured data serialization.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Jason Orendorff <jorendorff@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "jsclone.h"
|
||||
#include "jsdate.h"
|
||||
#include "jsregexp.h"
|
||||
#include "jstypedarray.h"
|
||||
|
||||
#include "jsregexpinlines.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
namespace js
|
||||
{
|
||||
|
||||
bool
|
||||
WriteStructuredClone(JSContext *cx, const Value &v, uint64 **bufp, size_t *nbytesp)
|
||||
{
|
||||
SCOutput out(cx);
|
||||
JSStructuredCloneWriter w(out);
|
||||
return w.init() && w.write(v) && out.extractBuffer(bufp, nbytesp);
|
||||
}
|
||||
|
||||
bool
|
||||
ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp)
|
||||
{
|
||||
SCInput in(cx, data, nbytes);
|
||||
JSStructuredCloneReader r(in);
|
||||
return r.read(vp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum StructuredDataType {
|
||||
/* Structured data types provided by the engine */
|
||||
SCTAG_FLOAT_MAX = 0xFFF00000,
|
||||
SCTAG_NULL = 0xFFFF0000,
|
||||
SCTAG_UNDEFINED,
|
||||
SCTAG_BOOLEAN,
|
||||
SCTAG_INDEX,
|
||||
SCTAG_STRING,
|
||||
SCTAG_DATE_OBJECT,
|
||||
SCTAG_REGEXP_OBJECT,
|
||||
SCTAG_ARRAY_OBJECT,
|
||||
SCTAG_OBJECT_OBJECT,
|
||||
SCTAG_ARRAY_BUFFER_OBJECT,
|
||||
SCTAG_TYPED_ARRAY_MIN = 0xFFFF0100,
|
||||
SCTAG_TYPED_ARRAY_MAX = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_MAX - 1,
|
||||
SCTAG_END_OF_BUILTIN_TYPES
|
||||
};
|
||||
|
||||
JS_STATIC_ASSERT(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
|
||||
JS_STATIC_ASSERT(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX);
|
||||
|
||||
static uint8_t
|
||||
SwapBytes(uint8_t u)
|
||||
{
|
||||
return u;
|
||||
}
|
||||
|
||||
static uint16_t
|
||||
SwapBytes(uint16_t u)
|
||||
{
|
||||
#ifdef IS_BIG_ENDIAN
|
||||
return ((u & 0x00ff) << 8) | ((u & 0xff00) >> 8);
|
||||
#else
|
||||
return u;
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
SwapBytes(uint32_t u)
|
||||
{
|
||||
#ifdef IS_BIG_ENDIAN
|
||||
return ((u & 0x000000ffU) << 24) |
|
||||
((u & 0x0000ff00U) << 8) |
|
||||
((u & 0x00ff0000U) >> 8) |
|
||||
((u & 0xff000000U) >> 24);
|
||||
#else
|
||||
return u;
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
SwapBytes(uint64_t u)
|
||||
{
|
||||
#ifdef IS_BIG_ENDIAN
|
||||
return ((u & 0x00000000000000ffLLU) << 56) |
|
||||
((u & 0x000000000000ff00LLU) << 40) |
|
||||
((u & 0x0000000000ff0000LLU) << 24) |
|
||||
((u & 0x00000000ff000000LLU) << 8) |
|
||||
((u & 0x000000ff00000000LLU) >> 8) |
|
||||
((u & 0x0000ff0000000000LLU) >> 24) |
|
||||
((u & 0x00ff000000000000LLU) >> 40) |
|
||||
((u & 0xff00000000000000LLU) >> 56);
|
||||
#else
|
||||
return u;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
SCInput::eof()
|
||||
{
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "truncated");
|
||||
return false;
|
||||
}
|
||||
|
||||
SCInput::SCInput(JSContext *cx, const uint64_t *data, size_t nbytes)
|
||||
: cx(cx), point(data), end(data + nbytes / 8)
|
||||
{
|
||||
JS_ASSERT((uintptr_t(data) & 7) == 0);
|
||||
JS_ASSERT((nbytes & 7) == 0);
|
||||
}
|
||||
|
||||
bool
|
||||
SCInput::read(uint64_t *p)
|
||||
{
|
||||
if (point == end)
|
||||
return eof();
|
||||
*p = SwapBytes(*point++);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SCInput::readPair(uint32_t *tagp, uint32_t *datap)
|
||||
{
|
||||
uint64_t u;
|
||||
bool ok = read(&u);
|
||||
if (ok) {
|
||||
*tagp = uint32_t(u >> 32);
|
||||
*datap = uint32_t(u);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool
|
||||
SCInput::readDouble(jsdouble *p)
|
||||
{
|
||||
union {
|
||||
uint64_t u;
|
||||
jsdouble d;
|
||||
} pun;
|
||||
if (!read(&pun.u))
|
||||
return false;
|
||||
*p = JS_CANONICALIZE_NAN(pun.d);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool
|
||||
SCInput::readArray(T *p, size_t nelems)
|
||||
{
|
||||
JS_STATIC_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
|
||||
|
||||
/*
|
||||
* Fail if nelems is so huge as to make JS_HOWMANY overflow or if nwords is
|
||||
* larger than the remaining data.
|
||||
*/
|
||||
size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
|
||||
if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems || nwords > size_t(end - point))
|
||||
return eof();
|
||||
|
||||
if (sizeof(T) == 1) {
|
||||
memcpy(p, point, nelems);
|
||||
} else {
|
||||
const T *q = (const T *) point;
|
||||
const T *qend = q + nelems;
|
||||
while (q != qend)
|
||||
*p++ = SwapBytes(*q++);
|
||||
}
|
||||
point += nwords;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SCInput::readBytes(void *p, size_t nbytes)
|
||||
{
|
||||
return readArray((uint8_t *) p, nbytes);
|
||||
}
|
||||
|
||||
bool
|
||||
SCInput::readChars(jschar *p, size_t nchars)
|
||||
{
|
||||
JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
|
||||
return readArray((uint16_t *) p, nchars);
|
||||
}
|
||||
|
||||
SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {}
|
||||
|
||||
bool
|
||||
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)
|
||||
{
|
||||
/*
|
||||
* As it happens, the tag word appears after the data word in the output.
|
||||
* This is because exponents occupy the last 2 bytes of jsdoubles on the
|
||||
* little-endian platforms we care most about.
|
||||
*
|
||||
* For example, JSVAL_TRUE is written using writePair(SCTAG_BOOLEAN, 1).
|
||||
* PairToUInt64 produces the number 0xFFFF000200000001.
|
||||
* That is written out as the bytes 01 00 00 00 02 00 FF FF.
|
||||
*/
|
||||
return write(PairToUInt64(tag, data));
|
||||
}
|
||||
|
||||
static inline uint64_t
|
||||
ReinterpretDoubleAsUInt64(jsdouble d)
|
||||
{
|
||||
union {
|
||||
jsdouble d;
|
||||
uint64_t u;
|
||||
} pun;
|
||||
pun.d = d;
|
||||
return pun.u;
|
||||
}
|
||||
|
||||
static inline jsdouble
|
||||
ReinterpretUInt64AsDouble(uint64_t u)
|
||||
{
|
||||
union {
|
||||
uint64_t u;
|
||||
jsdouble d;
|
||||
} pun;
|
||||
pun.u = u;
|
||||
return pun.d;
|
||||
}
|
||||
|
||||
static inline jsdouble
|
||||
ReinterpretPairAsDouble(uint32_t tag, uint32_t data)
|
||||
{
|
||||
return ReinterpretUInt64AsDouble(PairToUInt64(tag, data));
|
||||
}
|
||||
|
||||
static inline bool
|
||||
IsNonCanonicalizedNaN(jsdouble d)
|
||||
{
|
||||
return ReinterpretDoubleAsUInt64(d) != ReinterpretDoubleAsUInt64(JS_CANONICALIZE_NAN(d));
|
||||
}
|
||||
|
||||
bool
|
||||
SCOutput::writeDouble(jsdouble d)
|
||||
{
|
||||
JS_ASSERT(!IsNonCanonicalizedNaN(d));
|
||||
return write(ReinterpretDoubleAsUInt64(d));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool
|
||||
SCOutput::writeArray(const T *p, size_t nelems)
|
||||
{
|
||||
JS_ASSERT(8 % sizeof(T) == 0);
|
||||
JS_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
|
||||
|
||||
if (nelems == 0)
|
||||
return true;
|
||||
|
||||
if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems) {
|
||||
js_ReportAllocationOverflow(context());
|
||||
return false;
|
||||
}
|
||||
uint64_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
|
||||
size_t start = buf.length();
|
||||
if (!buf.growByUninitialized(nwords))
|
||||
return false;
|
||||
|
||||
buf.back() = 0; /* zero-pad to an 8-byte boundary */
|
||||
|
||||
T *q = (T *) &buf[start];
|
||||
if (sizeof(T) == 1) {
|
||||
memcpy(q, p, nelems);
|
||||
} else {
|
||||
const T *pend = p + nelems;
|
||||
while (p != pend)
|
||||
*q++ = SwapBytes(*p++);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SCOutput::writeBytes(const void *p, size_t nbytes)
|
||||
{
|
||||
return writeArray((const uint8_t *) p, nbytes);
|
||||
}
|
||||
|
||||
bool
|
||||
SCOutput::writeChars(const jschar *p, size_t nchars)
|
||||
{
|
||||
JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
|
||||
return writeArray((const uint16_t *) p, nchars);
|
||||
}
|
||||
|
||||
bool
|
||||
SCOutput::extractBuffer(uint64_t **datap, size_t *sizep)
|
||||
{
|
||||
*sizep = buf.length() * sizeof(uint64_t);
|
||||
return (*datap = buf.extractRawBuffer()) != NULL;
|
||||
}
|
||||
|
||||
JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
|
||||
|
||||
bool
|
||||
JSStructuredCloneWriter::writeString(JSString *str)
|
||||
{
|
||||
const jschar *chars;
|
||||
size_t length;
|
||||
str->getCharsAndLength(chars, length);
|
||||
return out.writePair(SCTAG_STRING, uint32_t(length)) && out.writeChars(chars, length);
|
||||
}
|
||||
|
||||
bool
|
||||
JSStructuredCloneWriter::writeId(jsid id)
|
||||
{
|
||||
if (JSID_IS_INT(id))
|
||||
return out.writePair(SCTAG_INDEX, uint32_t(JSID_TO_INT(id)));
|
||||
JS_ASSERT(JSID_IS_STRING(id));
|
||||
return writeString(JSID_TO_STRING(id));
|
||||
}
|
||||
|
||||
inline void
|
||||
JSStructuredCloneWriter::checkStack()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
/* To avoid making serialization O(n^2), limit stack-checking at 10. */
|
||||
const size_t MAX = 10;
|
||||
|
||||
size_t limit = JS_MIN(counts.length(), MAX);
|
||||
JS_ASSERT(objs.length() == counts.length());
|
||||
size_t total = 0;
|
||||
for (size_t i = 0; i < limit; i++) {
|
||||
JS_ASSERT(total + counts[i] >= total);
|
||||
total += counts[i];
|
||||
}
|
||||
if (counts.length() <= MAX)
|
||||
JS_ASSERT(total == ids.length());
|
||||
else
|
||||
JS_ASSERT(total <= ids.length());
|
||||
|
||||
JS_ASSERT(memory.count() == objs.length());
|
||||
size_t j = objs.length();
|
||||
for (size_t i = 0; i < limit; i++)
|
||||
JS_ASSERT(memory.has(&objs[--j].toObject()));
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
ArrayTypeToTag(uint32_t type)
|
||||
{
|
||||
/*
|
||||
* As long as these are all true, we can just add. Note that for backward
|
||||
* compatibility, the tags cannot change. So if the ArrayType type codes
|
||||
* change, this function and TagToArrayType will have to do more work.
|
||||
*/
|
||||
JS_STATIC_ASSERT(TypedArray::TYPE_INT8 == 0);
|
||||
JS_STATIC_ASSERT(TypedArray::TYPE_UINT8 == 1);
|
||||
JS_STATIC_ASSERT(TypedArray::TYPE_INT16 == 2);
|
||||
JS_STATIC_ASSERT(TypedArray::TYPE_UINT16 == 3);
|
||||
JS_STATIC_ASSERT(TypedArray::TYPE_INT32 == 4);
|
||||
JS_STATIC_ASSERT(TypedArray::TYPE_UINT32 == 5);
|
||||
JS_STATIC_ASSERT(TypedArray::TYPE_FLOAT32 == 6);
|
||||
JS_STATIC_ASSERT(TypedArray::TYPE_FLOAT64 == 7);
|
||||
JS_STATIC_ASSERT(TypedArray::TYPE_UINT8_CLAMPED == 8);
|
||||
JS_STATIC_ASSERT(TypedArray::TYPE_MAX == TypedArray::TYPE_UINT8_CLAMPED + 1);
|
||||
|
||||
JS_ASSERT(type < TypedArray::TYPE_MAX);
|
||||
return SCTAG_TYPED_ARRAY_MIN + type;
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
TagToArrayType(uint32_t tag)
|
||||
{
|
||||
JS_ASSERT(SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX);
|
||||
return tag - SCTAG_TYPED_ARRAY_MIN;
|
||||
}
|
||||
|
||||
bool
|
||||
JSStructuredCloneWriter::writeTypedArray(JSObject *obj)
|
||||
{
|
||||
TypedArray *arr = TypedArray::fromJSObject(obj);
|
||||
if (!out.writePair(ArrayTypeToTag(arr->type), arr->length))
|
||||
return false;
|
||||
|
||||
switch (arr->type) {
|
||||
case TypedArray::TYPE_INT8:
|
||||
case TypedArray::TYPE_UINT8:
|
||||
case TypedArray::TYPE_UINT8_CLAMPED:
|
||||
return out.writeArray((const uint8_t *) arr->data, arr->length);
|
||||
case TypedArray::TYPE_INT16:
|
||||
case TypedArray::TYPE_UINT16:
|
||||
return out.writeArray((const uint16_t *) arr->data, arr->length);
|
||||
case TypedArray::TYPE_INT32:
|
||||
case TypedArray::TYPE_UINT32:
|
||||
case TypedArray::TYPE_FLOAT32:
|
||||
return out.writeArray((const uint32_t *) arr->data, arr->length);
|
||||
case TypedArray::TYPE_FLOAT64:
|
||||
return out.writeArray((const uint64_t *) arr->data, arr->length);
|
||||
default:
|
||||
JS_NOT_REACHED("unknown TypedArray type");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
JSStructuredCloneWriter::writeArrayBuffer(JSObject *obj)
|
||||
{
|
||||
ArrayBuffer *abuf = ArrayBuffer::fromJSObject(obj);
|
||||
return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, abuf->byteLength) &&
|
||||
out.writeBytes(abuf->data, abuf->byteLength);
|
||||
}
|
||||
|
||||
bool
|
||||
JSStructuredCloneWriter::startObject(JSObject *obj)
|
||||
{
|
||||
JS_ASSERT(obj->isArray() || obj->isObject());
|
||||
|
||||
/* Fail if obj is already on the stack. */
|
||||
HashSet<JSObject *>::AddPtr p = memory.lookupForAdd(obj);
|
||||
if (p) {
|
||||
JSContext *cx = context();
|
||||
const JSStructuredCloneCallbacks *cb = cx->runtime->structuredCloneCallbacks;
|
||||
if (cb && cb->reportError)
|
||||
cb->reportError(cx, JS_SCERR_RECURSION);
|
||||
else
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SC_RECURSION);
|
||||
return false;
|
||||
}
|
||||
if (!memory.add(p, obj))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Get enumerable property ids and put them in reverse order so that they
|
||||
* will come off the stack in forward order.
|
||||
*/
|
||||
size_t initialLength = ids.length();
|
||||
if (!GetPropertyNames(context(), obj, JSITER_OWNONLY, &ids))
|
||||
return false;
|
||||
jsid *begin = ids.begin() + initialLength, *end = ids.end();
|
||||
size_t count = size_t(end - begin);
|
||||
Reverse(begin, end);
|
||||
|
||||
/* Push obj and count to the stack. */
|
||||
if (!objs.append(ObjectValue(*obj)) || !counts.append(count))
|
||||
return false;
|
||||
checkStack();
|
||||
|
||||
/* Write the header for obj. */
|
||||
return out.writePair(obj->isArray() ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
|
||||
}
|
||||
|
||||
bool
|
||||
JSStructuredCloneWriter::startWrite(const js::Value &v)
|
||||
{
|
||||
if (v.isString()) {
|
||||
return writeString(v.toString());
|
||||
} else if (v.isNumber()) {
|
||||
return out.writeDouble(v.toNumber());
|
||||
} else if (v.isBoolean()) {
|
||||
return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
|
||||
} else if (v.isNull()) {
|
||||
return out.writePair(SCTAG_NULL, 0);
|
||||
} else if (v.isUndefined()) {
|
||||
return out.writePair(SCTAG_UNDEFINED, 0);
|
||||
} else if (v.isObject()) {
|
||||
JSObject *obj = &v.toObject();
|
||||
if (obj->isRegExp()) {
|
||||
RegExp *re = RegExp::extractFrom(obj);
|
||||
return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags()) &&
|
||||
writeString(re->getSource());
|
||||
} else if (obj->isDate()) {
|
||||
jsdouble d = js_DateGetMsecSinceEpoch(context(), obj);
|
||||
return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(d);
|
||||
} else if (obj->isObject() || obj->isArray()) {
|
||||
return startObject(obj);
|
||||
} else if (js_IsTypedArray(obj)) {
|
||||
return writeTypedArray(obj);
|
||||
} else if (js_IsArrayBuffer(obj) && ArrayBuffer::fromJSObject(obj)) {
|
||||
return writeArrayBuffer(obj);
|
||||
}
|
||||
/* else fall through */
|
||||
}
|
||||
|
||||
/*
|
||||
* v is either an object or some strange value like JSVAL_HOLE. Even in the
|
||||
* latter case, we call the write op anyway, to let the application throw
|
||||
* the NOT_SUPPORTED_ERR exception.
|
||||
*/
|
||||
JSContext *cx = context();
|
||||
JSRuntime *rt = cx->runtime;
|
||||
if (rt->structuredCloneCallbacks)
|
||||
return rt->structuredCloneCallbacks->write(cx, this, Jsvalify(v));
|
||||
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SC_UNSUPPORTED_TYPE);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
JSStructuredCloneWriter::write(const Value &v)
|
||||
{
|
||||
if (!startWrite(v))
|
||||
return false;
|
||||
|
||||
while (!counts.empty()) {
|
||||
JSObject *obj = &objs.back().toObject();
|
||||
if (counts.back()) {
|
||||
counts.back()--;
|
||||
jsid id = ids.back();
|
||||
ids.popBack();
|
||||
checkStack();
|
||||
if (JSID_IS_STRING(id) || JSID_IS_INT(id)) {
|
||||
/*
|
||||
* If obj still has an own property named id, write it out.
|
||||
* The cost of re-checking could be avoided by using
|
||||
* NativeIterators.
|
||||
*/
|
||||
JSObject *obj2;
|
||||
JSProperty *prop;
|
||||
if (!js_HasOwnProperty(context(), obj->getOps()->lookupProperty, obj, id,
|
||||
&obj2, &prop)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prop) {
|
||||
obj2->dropProperty(context(), prop);
|
||||
|
||||
Value val;
|
||||
if (!writeId(id) || !obj->getProperty(context(), id, &val) || !startWrite(val))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.writePair(SCTAG_NULL, 0);
|
||||
memory.remove(obj);
|
||||
objs.popBack();
|
||||
counts.popBack();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class Chars {
|
||||
JSContext *cx;
|
||||
jschar *p;
|
||||
public:
|
||||
Chars() : p(NULL) {}
|
||||
~Chars() { if (p) cx->free(p); }
|
||||
|
||||
bool allocate(JSContext *cx, size_t len) {
|
||||
JS_ASSERT(!p);
|
||||
p = (jschar *) cx->malloc(len * sizeof(jschar));
|
||||
this->cx = cx;
|
||||
return p != NULL;
|
||||
}
|
||||
jschar *get() { return p; }
|
||||
void forget() { p = NULL; }
|
||||
};
|
||||
|
||||
JSString *
|
||||
JSStructuredCloneReader::readString(uint32_t nchars)
|
||||
{
|
||||
if (nchars > JSString::MAX_LENGTH) {
|
||||
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
|
||||
"string length");
|
||||
return NULL;
|
||||
}
|
||||
Chars chars;
|
||||
if (!chars.allocate(context(), nchars) || !in.readChars(chars.get(), nchars))
|
||||
return NULL;
|
||||
JSString *str = js_NewString(context(), chars.get(), nchars);
|
||||
if (str)
|
||||
chars.forget();
|
||||
return str;
|
||||
}
|
||||
|
||||
bool
|
||||
JSStructuredCloneReader::readTypedArray(uint32_t tag, uint32_t nelems, Value *vp)
|
||||
{
|
||||
uint32_t atype = TagToArrayType(tag);
|
||||
JSObject *obj = js_CreateTypedArray(context(), atype, nelems);
|
||||
if (!obj)
|
||||
return false;
|
||||
vp->setObject(*obj);
|
||||
|
||||
TypedArray *arr = TypedArray::fromJSObject(obj);
|
||||
JS_ASSERT(arr->length == nelems);
|
||||
JS_ASSERT(arr->type == atype);
|
||||
switch (atype) {
|
||||
case TypedArray::TYPE_INT8:
|
||||
case TypedArray::TYPE_UINT8:
|
||||
case TypedArray::TYPE_UINT8_CLAMPED:
|
||||
return in.readArray((uint8_t *) arr->data, nelems);
|
||||
case TypedArray::TYPE_INT16:
|
||||
case TypedArray::TYPE_UINT16:
|
||||
return in.readArray((uint16_t *) arr->data, nelems);
|
||||
case TypedArray::TYPE_INT32:
|
||||
case TypedArray::TYPE_UINT32:
|
||||
case TypedArray::TYPE_FLOAT32:
|
||||
return in.readArray((uint32_t *) arr->data, nelems);
|
||||
case TypedArray::TYPE_FLOAT64:
|
||||
return in.readArray((uint64_t *) arr->data, nelems);
|
||||
default:
|
||||
JS_NOT_REACHED("unknown TypedArray type");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, Value *vp)
|
||||
{
|
||||
JSObject *obj = js_CreateArrayBuffer(context(), nbytes);
|
||||
if (!obj)
|
||||
return false;
|
||||
vp->setObject(*obj);
|
||||
ArrayBuffer *abuf = ArrayBuffer::fromJSObject(obj);
|
||||
JS_ASSERT(abuf->byteLength == nbytes);
|
||||
return in.readArray((uint8_t *) abuf->data, nbytes);
|
||||
}
|
||||
|
||||
bool
|
||||
JSStructuredCloneReader::startRead(Value *vp)
|
||||
{
|
||||
uint32_t tag, data;
|
||||
|
||||
if (!in.readPair(&tag, &data))
|
||||
return false;
|
||||
switch (tag) {
|
||||
case SCTAG_NULL:
|
||||
vp->setNull();
|
||||
break;
|
||||
|
||||
case SCTAG_UNDEFINED:
|
||||
vp->setUndefined();
|
||||
break;
|
||||
|
||||
case SCTAG_BOOLEAN:
|
||||
vp->setBoolean(!!data);
|
||||
break;
|
||||
|
||||
case SCTAG_STRING: {
|
||||
JSString *str = readString(data);
|
||||
if (!str)
|
||||
return false;
|
||||
vp->setString(str);
|
||||
break;
|
||||
}
|
||||
|
||||
case SCTAG_DATE_OBJECT: {
|
||||
jsdouble d;
|
||||
if (!in.readDouble(&d))
|
||||
return false;
|
||||
JSObject *obj = js_NewDateObjectMsec(context(), d);
|
||||
if (!obj)
|
||||
return false;
|
||||
vp->setObject(*obj);
|
||||
break;
|
||||
}
|
||||
|
||||
case SCTAG_REGEXP_OBJECT: {
|
||||
uint32_t tag2, nchars;
|
||||
if (!in.readPair(&tag2, &nchars))
|
||||
return false;
|
||||
if (tag2 != SCTAG_STRING) {
|
||||
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
|
||||
"regexp");
|
||||
return false;
|
||||
}
|
||||
JSString *str = readString(nchars);
|
||||
if (!str)
|
||||
return false;
|
||||
const jschar *chars;
|
||||
size_t length;
|
||||
str->getCharsAndLength(chars, length);
|
||||
JSObject *obj = RegExp::createObjectNoStatics(context(), chars, length, data);
|
||||
if (!obj)
|
||||
return false;
|
||||
vp->setObject(*obj);
|
||||
break;
|
||||
}
|
||||
|
||||
case SCTAG_ARRAY_OBJECT:
|
||||
case SCTAG_OBJECT_OBJECT: {
|
||||
JSObject *obj = (tag == SCTAG_ARRAY_OBJECT)
|
||||
? js_NewArrayObject(context(), 0, NULL)
|
||||
: NewBuiltinClassInstance(context(), &js_ObjectClass);
|
||||
if (!obj || !objs.append(ObjectValue(*obj)))
|
||||
return false;
|
||||
vp->setObject(*obj);
|
||||
break;
|
||||
}
|
||||
|
||||
case SCTAG_ARRAY_BUFFER_OBJECT:
|
||||
return readArrayBuffer(data, vp);
|
||||
|
||||
default:
|
||||
if (tag <= SCTAG_FLOAT_MAX) {
|
||||
jsdouble d = ReinterpretPairAsDouble(tag, data);
|
||||
if (IsNonCanonicalizedNaN(d)) {
|
||||
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
|
||||
JSMSG_SC_BAD_SERIALIZED_DATA, "unrecognized NaN");
|
||||
return false;
|
||||
}
|
||||
vp->setNumber(d);
|
||||
break;
|
||||
}
|
||||
|
||||
if (SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX)
|
||||
return readTypedArray(tag, data, vp);
|
||||
|
||||
JSRuntime *rt = context()->runtime;
|
||||
if (!rt->structuredCloneCallbacks) {
|
||||
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
|
||||
"unsupported type");
|
||||
return false;
|
||||
}
|
||||
return rt->structuredCloneCallbacks->read(context(), this, tag, data, Jsvalify(vp));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
JSStructuredCloneReader::readId(jsid *idp)
|
||||
{
|
||||
uint32_t tag, data;
|
||||
if (!in.readPair(&tag, &data))
|
||||
return false;
|
||||
|
||||
if (tag == SCTAG_INDEX) {
|
||||
*idp = INT_TO_JSID(int32_t(data));
|
||||
return true;
|
||||
}
|
||||
if (tag == SCTAG_STRING) {
|
||||
JSString *str = readString(data);
|
||||
if (!str)
|
||||
return false;
|
||||
JSAtom *atom = js_AtomizeString(context(), str, 0);
|
||||
if (!atom)
|
||||
return false;
|
||||
*idp = ATOM_TO_JSID(atom);
|
||||
return true;
|
||||
}
|
||||
if (tag == SCTAG_NULL) {
|
||||
*idp = JSID_VOID;
|
||||
return true;
|
||||
}
|
||||
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "id");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
JSStructuredCloneReader::read(Value *vp)
|
||||
{
|
||||
if (!startRead(vp))
|
||||
return false;
|
||||
|
||||
while (objs.length() != 0) {
|
||||
JSObject *obj = &objs.back().toObject();
|
||||
|
||||
jsid id;
|
||||
if (!readId(&id))
|
||||
return false;
|
||||
|
||||
if (JSID_IS_VOID(id)) {
|
||||
objs.popBack();
|
||||
} else {
|
||||
Value v;
|
||||
if (!startRead(&v) || !obj->defineProperty(context(), id, v))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
174
js/src/jsclone.h
Normal file
174
js/src/jsclone.h
Normal file
@ -0,0 +1,174 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is JavaScript structured data serialization.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Jason Orendorff <jorendorff@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef jsclone_h___
|
||||
#define jsclone_h___
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "jscntxt.h"
|
||||
#include "jshashtable.h"
|
||||
#include "jsstdint.h"
|
||||
#include "jsvector.h"
|
||||
#include "jsvalue.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
bool
|
||||
WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp);
|
||||
|
||||
bool
|
||||
ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp);
|
||||
|
||||
struct SCOutput {
|
||||
public:
|
||||
explicit SCOutput(JSContext *cx);
|
||||
|
||||
JSContext *context() const { return cx; }
|
||||
|
||||
bool write(uint64_t u);
|
||||
bool writePair(uint32_t tag, uint32_t data);
|
||||
bool writeDouble(jsdouble d);
|
||||
bool writeBytes(const void *p, size_t nbytes);
|
||||
bool writeChars(const jschar *p, size_t nchars);
|
||||
|
||||
template <class T>
|
||||
bool writeArray(const T *p, size_t nbytes);
|
||||
|
||||
bool extractBuffer(uint64_t **datap, size_t *sizep);
|
||||
|
||||
private:
|
||||
JSContext *cx;
|
||||
js::Vector<uint64_t> buf;
|
||||
};
|
||||
|
||||
struct SCInput {
|
||||
public:
|
||||
SCInput(JSContext *cx, const uint64_t *data, size_t nbytes);
|
||||
|
||||
JSContext *context() const { return cx; }
|
||||
|
||||
bool read(uint64_t *p);
|
||||
bool readPair(uint32_t *tagp, uint32_t *datap);
|
||||
bool readDouble(jsdouble *p);
|
||||
bool readBytes(void *p, size_t nbytes);
|
||||
bool readChars(jschar *p, size_t nchars);
|
||||
|
||||
template <class T>
|
||||
bool readArray(T *p, size_t nelems);
|
||||
|
||||
private:
|
||||
bool eof();
|
||||
|
||||
void staticAssertions() {
|
||||
JS_STATIC_ASSERT(sizeof(jschar) == 2);
|
||||
JS_STATIC_ASSERT(sizeof(uint32_t) == 4);
|
||||
JS_STATIC_ASSERT(sizeof(jsdouble) == 8);
|
||||
}
|
||||
|
||||
JSContext *cx;
|
||||
const uint64_t *point;
|
||||
const uint64_t *end;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
struct JSStructuredCloneReader {
|
||||
public:
|
||||
explicit JSStructuredCloneReader(js::SCInput &in)
|
||||
: in(in), objs(in.context()) {}
|
||||
|
||||
js::SCInput &input() { return in; }
|
||||
bool read(js::Value *vp);
|
||||
|
||||
private:
|
||||
JSContext *context() { return in.context(); }
|
||||
|
||||
JSString *readString(uint32_t nchars);
|
||||
bool readTypedArray(uint32_t tag, uint32_t nelems, js::Value *vp);
|
||||
bool readArrayBuffer(uint32_t nbytes, js::Value *vp);
|
||||
bool readId(jsid *idp);
|
||||
bool startRead(js::Value *vp);
|
||||
|
||||
js::SCInput ∈
|
||||
|
||||
// Stack of objects with properties remaining to be read.
|
||||
js::AutoValueVector objs;
|
||||
};
|
||||
|
||||
struct JSStructuredCloneWriter {
|
||||
public:
|
||||
explicit JSStructuredCloneWriter(js::SCOutput &out)
|
||||
: out(out), objs(out.context()), counts(out.context()), ids(out.context()),
|
||||
memory(out.context()) {}
|
||||
|
||||
bool init() { return memory.init(); }
|
||||
|
||||
bool write(const js::Value &v);
|
||||
|
||||
js::SCOutput &output() { return out; }
|
||||
|
||||
private:
|
||||
JSContext *context() { return out.context(); }
|
||||
|
||||
bool writeString(JSString *str);
|
||||
bool writeId(jsid id);
|
||||
bool writeArrayBuffer(JSObject *obj);
|
||||
bool writeTypedArray(JSObject *obj);
|
||||
bool startObject(JSObject *obj);
|
||||
bool startWrite(const js::Value &v);
|
||||
|
||||
inline void checkStack();
|
||||
|
||||
js::SCOutput &out;
|
||||
|
||||
// Stack of objects with properties remaining to be written.
|
||||
js::AutoValueVector objs;
|
||||
|
||||
// counts[i] is the number of properties of objs[i] remaining to be written.
|
||||
// counts.length() == objs.length() and sum(counts) == ids.length().
|
||||
js::Vector<size_t> counts;
|
||||
|
||||
// Ids of properties remaining to be written.
|
||||
js::AutoIdVector ids;
|
||||
|
||||
// The "memory" list described in the HTML5 internal structured cloning algorithm.
|
||||
// memory has the same elements as objs.
|
||||
js::HashSet<JSObject *> memory;
|
||||
};
|
||||
|
||||
#endif /* jsclone_h___ */
|
@ -1478,6 +1478,9 @@ struct JSRuntime {
|
||||
*/
|
||||
JSSecurityCallbacks *securityCallbacks;
|
||||
|
||||
/* Structured data callbacks are runtime-wide. */
|
||||
const JSStructuredCloneCallbacks *structuredCloneCallbacks;
|
||||
|
||||
/*
|
||||
* Shared scope property tree, and arena-pool for allocating its nodes.
|
||||
* This really should be free of all locking overhead and allocated in
|
||||
|
@ -3434,7 +3434,7 @@ BEGIN_CASE(JSOP_URSH)
|
||||
u >>= (j & 31);
|
||||
|
||||
regs.sp--;
|
||||
regs.sp[-1].setNumber(uint32(u));
|
||||
regs.sp[-1].setNumber(uint32(u));
|
||||
}
|
||||
END_CASE(JSOP_URSH)
|
||||
|
||||
|
@ -408,7 +408,7 @@ ParseIntDoubleHelper(jsdouble d)
|
||||
if (d > 0)
|
||||
return floor(d);
|
||||
if (d < 0)
|
||||
return -floor(-d);
|
||||
return -floor(-d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -165,6 +165,10 @@ typedef struct JSSecurityCallbacks JSSecurityCallbacks;
|
||||
typedef struct JSONParser JSONParser;
|
||||
typedef struct JSCompartment JSCompartment;
|
||||
typedef struct JSCrossCompartmentCall JSCrossCompartmentCall;
|
||||
typedef struct JSStructuredCloneWriter JSStructuredCloneWriter;
|
||||
typedef struct JSStructuredCloneReader JSStructuredCloneReader;
|
||||
typedef struct JSStructuredCloneCallbacks JSStructuredCloneCallbacks;
|
||||
|
||||
#ifdef __cplusplus
|
||||
typedef class JSWrapper JSWrapper;
|
||||
typedef class JSCrossCompartmentWrapper JSCrossCompartmentWrapper;
|
||||
@ -565,6 +569,37 @@ typedef enum {
|
||||
typedef JSBool
|
||||
(* JSCompartmentCallback)(JSContext *cx, JSCompartment *compartment, uintN compartmentOp);
|
||||
|
||||
/*
|
||||
* Read structured data from the reader r. This hook is used to read a value
|
||||
* previously serialized by a call to the WriteStructuredCloneOp hook.
|
||||
*
|
||||
* tag and data are the pair of uint32 values from the header. The callback may
|
||||
* use the JS_Read* APIs to read any other relevant parts of the object from
|
||||
* the reader r. On success, it stores an object in *vp and returns JS_TRUE.
|
||||
*/
|
||||
typedef JSBool (*ReadStructuredCloneOp)(JSContext *cx, JSStructuredCloneReader *r,
|
||||
uint32 tag, uint32 data, jsval *vp);
|
||||
|
||||
/*
|
||||
* Structured data serialization hook. The engine can write primitive values,
|
||||
* Objects, Arrays, Dates, and RegExps. Any other type of object requires
|
||||
* application support. This callback must first use the JS_WritePair API to
|
||||
* write an object header, passing a value greater than JS_SCTAG_USER to the
|
||||
* tag parameter. Then it can use the JS_Write* APIs to write any other
|
||||
* relevant parts of the value v to the writer w.
|
||||
*
|
||||
* If !JSVAL_IS_OBJECT(v), then the callback is expected to report an
|
||||
* appropriate (application-specific) error and return JS_FALSE.
|
||||
*/
|
||||
typedef JSBool (*WriteStructuredCloneOp)(JSContext *cx, JSStructuredCloneWriter *w, jsval v);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
typedef void (*StructuredCloneErrorOp)(JSContext *cx, uint32 errorid);
|
||||
|
||||
JS_END_EXTERN_C
|
||||
|
||||
#endif /* jspubtd_h___ */
|
||||
|
@ -55,4 +55,4 @@ MSG_DEF(JSSMSG_ASSERT_EQ_FAILED, 10, 2, JSEXN_NONE, "Assertion failed: go
|
||||
MSG_DEF(JSSMSG_ASSERT_EQ_FAILED_MSG, 11, 3, JSEXN_NONE, "Assertion failed: got {0}, expected {1}: {2}")
|
||||
MSG_DEF(JSSMSG_INVALID_ARGS, 12, 1, JSEXN_NONE, "{0}: invalid arguments")
|
||||
MSG_DEF(JSSMSG_ASSERT_JIT_FAILED, 13, 0, JSEXN_NONE, "unexpected failure to JIT")
|
||||
|
||||
MSG_DEF(JSSMSG_BAD_ALIGNMENT, 14, 0, JSEXN_NONE, "serialized data must be 8-byte-aligned")
|
||||
|
@ -13849,7 +13849,7 @@ TraceRecorder::denseArrayElement(Value& oval, Value& ival, Value*& vp, LIns*& v_
|
||||
LIns* dslots_ins =
|
||||
addName(lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, dslots), ACCSET_OTHER), "dslots");
|
||||
vp = &obj->dslots[jsuint(idx)];
|
||||
JS_ASSERT(sizeof(Value) == 8); // The |3| in the following statement requires this.
|
||||
JS_ASSERT(sizeof(Value) == 8); // The |3| in the following statement requires this.
|
||||
addr_ins = lir->ins2(LIR_addp, dslots_ins,
|
||||
lir->ins2ImmI(LIR_lshp, lir->insUI2P(idx_ins), 3));
|
||||
v_ins = unbox_value(*vp, addr_ins, 0, branchExit);
|
||||
|
@ -41,6 +41,7 @@
|
||||
#define jsvector_h_
|
||||
|
||||
#include "jstl.h"
|
||||
#include "jsprvtd.h"
|
||||
|
||||
/* Silence dire "bugs in previous versions of MSVC have been fixed" warnings */
|
||||
#ifdef _MSC_VER
|
||||
|
@ -75,6 +75,7 @@
|
||||
#include "jsscope.h"
|
||||
#include "jsscript.h"
|
||||
#include "jstracer.h"
|
||||
#include "jstypedarray.h"
|
||||
#include "jsxml.h"
|
||||
#include "jsperf.h"
|
||||
|
||||
@ -4099,8 +4100,6 @@ Snarl(JSContext *cx, uintN argc, jsval *vp)
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
JSBool
|
||||
Wrap(JSContext *cx, uintN argc, jsval *vp)
|
||||
{
|
||||
@ -4120,6 +4119,49 @@ Wrap(JSContext *cx, uintN argc, jsval *vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
JSBool
|
||||
Serialize(JSContext *cx, uintN argc, jsval *vp)
|
||||
{
|
||||
jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
|
||||
uint64 *datap;
|
||||
size_t nbytes;
|
||||
if (!JS_WriteStructuredClone(cx, v, &datap, &nbytes))
|
||||
return false;
|
||||
|
||||
JSObject *arrayobj = js_CreateTypedArray(cx, TypedArray::TYPE_UINT8, nbytes);
|
||||
if (!arrayobj) {
|
||||
JS_free(cx, datap);
|
||||
return false;
|
||||
}
|
||||
TypedArray *array = TypedArray::fromJSObject(arrayobj);
|
||||
JS_ASSERT((uintptr_t(array->data) & 7) == 0);
|
||||
memcpy(array->data, datap, nbytes);
|
||||
JS_free(cx, datap);
|
||||
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(arrayobj));
|
||||
return true;
|
||||
}
|
||||
|
||||
JSBool
|
||||
Deserialize(JSContext *cx, uintN argc, jsval *vp)
|
||||
{
|
||||
jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
|
||||
JSObject *obj;
|
||||
if (!JSVAL_IS_OBJECT(v) || !js_IsTypedArray((obj = JSVAL_TO_OBJECT(v)))) {
|
||||
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "deserialize");
|
||||
return false;
|
||||
}
|
||||
TypedArray *array = TypedArray::fromJSObject(obj);
|
||||
if ((uintptr_t(array->data) & 7) != 0) {
|
||||
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_BAD_ALIGNMENT);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JS_ReadStructuredClone(cx, (uint64 *) array->data, array->byteLength, &v))
|
||||
return false;
|
||||
JS_SET_RVAL(cx, vp, v);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* We use a mix of JS_FS and JS_FN to test both kinds of natives. */
|
||||
static JSFunctionSpec shell_functions[] = {
|
||||
JS_FN("version", Version, 0,0),
|
||||
@ -4216,6 +4258,8 @@ static JSFunctionSpec shell_functions[] = {
|
||||
JS_FN("elapsed", Elapsed, 0,0),
|
||||
JS_FN("parent", Parent, 1,0),
|
||||
JS_FN("wrap", Wrap, 1,0),
|
||||
JS_FN("serialize", Serialize, 1,0),
|
||||
JS_FN("deserialize", Deserialize, 1,0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
@ -4341,7 +4385,9 @@ static const char *const shell_help_messages[] = {
|
||||
" A negative value (default) means that the execution time is unlimited.",
|
||||
"elapsed() Execution time elapsed for the current context.",
|
||||
"parent(obj) Returns the parent of obj.\n",
|
||||
"wrap(obj) Wrap an object into a noop wrapper.\n"
|
||||
"wrap(obj) Wrap an object into a noop wrapper.\n",
|
||||
"serialize(sd) Serialize sd using JS_WriteStructuredClone. Returns a TypedArray.\n",
|
||||
"deserialize(a) Deserialize data generated by serialize.\n"
|
||||
};
|
||||
|
||||
/* Help messages must match shell functions. */
|
||||
|
58
js/src/tests/js1_8_5/extensions/clone-errors.js
Normal file
58
js/src/tests/js1_8_5/extensions/clone-errors.js
Normal file
@ -0,0 +1,58 @@
|
||||
// -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
function check(v) {
|
||||
try {
|
||||
serialize(v);
|
||||
} catch (exc) {
|
||||
return;
|
||||
}
|
||||
throw new Error("serializing " + uneval(v) + " should have failed with an exception");
|
||||
}
|
||||
|
||||
// Unsupported object types.
|
||||
check(new Error("oops"));
|
||||
check(this);
|
||||
check(Math);
|
||||
check(function () {});
|
||||
check(Proxy.create({enumerate: function () { return []; }}));
|
||||
check(<element/>);
|
||||
check(new Namespace("x"));
|
||||
check(new QName("x", "y"));
|
||||
|
||||
// A failing getter.
|
||||
check({get x() { throw new Error("fail"); }});
|
||||
|
||||
// Various recursive objects, i.e. those which the structured cloning
|
||||
// algorithm wants us to reject due to "memory".
|
||||
//
|
||||
// Recursive array.
|
||||
var a = [];
|
||||
a[0] = a;
|
||||
check(a);
|
||||
|
||||
// Recursive Object.
|
||||
var b = {};
|
||||
b.next = b;
|
||||
check(b);
|
||||
|
||||
// Mutually recursive objects.
|
||||
a[0] = b;
|
||||
b.next = a;
|
||||
check(a);
|
||||
check(b);
|
||||
|
||||
// A recursive object that doesn't fail until 'memory' contains lots of objects.
|
||||
a = [];
|
||||
b = a;
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
b[0] = {};
|
||||
b[1] = [];
|
||||
b = b[1];
|
||||
}
|
||||
b[0] = {owner: a};
|
||||
b[1] = [];
|
||||
check(a);
|
||||
|
||||
reportCompare(0, 0, "ok");
|
233
js/src/tests/js1_8_5/extensions/clone-object.js
Normal file
233
js/src/tests/js1_8_5/extensions/clone-object.js
Normal file
@ -0,0 +1,233 @@
|
||||
// -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
// Assert that cloning b does the right thing as far as we can tell.
|
||||
// Caveat: getters in b must produce the same value each time they're
|
||||
// called. We may call them several times.
|
||||
//
|
||||
// If desc is provided, then the very first thing we do to b is clone it.
|
||||
// (The self-modifying object test counts on this.)
|
||||
//
|
||||
function check(b, desc) {
|
||||
function classOf(obj) {
|
||||
return Object.prototype.toString.call(obj);
|
||||
}
|
||||
|
||||
function ownProperties(obj) {
|
||||
return Object.getOwnPropertyNames(obj).
|
||||
map(function (p) { return [p, Object.getOwnPropertyDescriptor(obj, p)]; });
|
||||
}
|
||||
|
||||
function isCloneable(pair) {
|
||||
return typeof pair[0] === 'string' && pair[1].enumerable;
|
||||
}
|
||||
|
||||
function notIndex(p) {
|
||||
var u = p >>> 0;
|
||||
return !("" + u == p && u != 0xffffffff);
|
||||
}
|
||||
|
||||
function assertIsCloneOf(a, b, path) {
|
||||
assertEq(a === b, false);
|
||||
|
||||
var ca = classOf(a);
|
||||
assertEq(ca, classOf(b), path);
|
||||
|
||||
assertEq(Object.getPrototypeOf(a),
|
||||
ca == "[object Object]" ? Object.prototype : Array.prototype,
|
||||
path);
|
||||
|
||||
// 'b', the original object, may have non-enumerable or XMLName
|
||||
// properties; ignore them. 'a', the clone, should not have any
|
||||
// non-enumerable properties (except .length, if it's an Array) or
|
||||
// XMLName properties.
|
||||
var pb = ownProperties(b).filter(isCloneable);
|
||||
var pa = ownProperties(a);
|
||||
for (var i = 0; i < pa.length; i++) {
|
||||
assertEq(typeof pa[i][0], "string", "clone should not have E4X properties " + path);
|
||||
if (!pa[i][1].enumerable) {
|
||||
if (Array.isArray(a) && pa[i][0] == "length") {
|
||||
// remove it so that the comparisons below will work
|
||||
pa.splice(i, 1);
|
||||
i--;
|
||||
} else {
|
||||
throw new Error("non-enumerable clone property " + uneval(pa[i][0]) + " " + path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that, apart from properties whose names are array indexes,
|
||||
// the enumerable properties appear in the same order.
|
||||
var aNames = pa.map(function (pair) { return pair[1]; }).filter(notIndex);
|
||||
var bNames = pa.map(function (pair) { return pair[1]; }).filter(notIndex);
|
||||
assertEq(aNames.join(","), bNames.join(","), path);
|
||||
|
||||
// Check that the lists are the same when including array indexes.
|
||||
function byName(a, b) { a = a[0]; b = b[0]; return a < b ? -1 : a === b ? 0 : 1; }
|
||||
pa.sort(byName);
|
||||
pb.sort(byName);
|
||||
assertEq(pa.length, pb.length, "should see the same number of properties " + path);
|
||||
for (var i = 0; i < pa.length; i++) {
|
||||
var aName = pa[i][0];
|
||||
var bName = pb[i][0];
|
||||
assertEq(aName, bName, path);
|
||||
|
||||
var path2 = path + "." + aName;
|
||||
var da = pa[i][1];
|
||||
var db = pb[i][1];
|
||||
assertEq(da.configurable, true, path2);
|
||||
assertEq(da.writable, true, path2);
|
||||
assertEq("value" in da, true, path2);
|
||||
var va = da.value;
|
||||
var vb = b[pb[i][0]];
|
||||
if (typeof va === "object" && va !== null)
|
||||
queue.push([va, vb, path2]);
|
||||
else
|
||||
assertEq(va, vb, path2);
|
||||
}
|
||||
}
|
||||
|
||||
var banner = "while testing clone of " + (desc || uneval(b));
|
||||
var a = deserialize(serialize(b));
|
||||
var queue = [[a, b, banner]];
|
||||
while (queue.length) {
|
||||
var triple = queue.shift();
|
||||
assertIsCloneOf(triple[0], triple[1], triple[2]);
|
||||
}
|
||||
|
||||
return a; // for further testing
|
||||
}
|
||||
|
||||
function test() {
|
||||
check({});
|
||||
check([]);
|
||||
check({x: 0});
|
||||
check({x: 0.7, p: "forty-two", y: null, z: undefined});
|
||||
check(Array.prototype);
|
||||
check(Object.prototype);
|
||||
|
||||
// before and after
|
||||
var b, a;
|
||||
|
||||
// Slow array.
|
||||
b = [, 1, 2, 3];
|
||||
b.expando = true;
|
||||
b[5] = 5;
|
||||
b[0] = 0;
|
||||
b[4] = 4;
|
||||
delete b[2];
|
||||
check(b);
|
||||
|
||||
// Check cloning properties other than basic data properties. (check()
|
||||
// asserts that the properties of the clone are configurable, writable,
|
||||
// enumerable data properties.)
|
||||
b = {};
|
||||
Object.defineProperties(b, {
|
||||
x: {enumerable: true, get: function () { return 12479; }},
|
||||
y: {enumerable: true, configurable: true, writable: false, value: 0},
|
||||
z: {enumerable: true, configurable: false, writable: true, value: 0},
|
||||
hidden: {enumerable:false, value: 1334}});
|
||||
check(b);
|
||||
|
||||
// Check corner cases involving property names.
|
||||
b = {"-1": -1,
|
||||
0xffffffff: null,
|
||||
0x100000000: null,
|
||||
"": 0,
|
||||
"\xff\x7f\u7fff\uffff\ufeff\ufffe": 1, // random unicode id
|
||||
"\ud800 \udbff \udc00 \udfff": 2}; // busted surrogate pairs
|
||||
check(b);
|
||||
|
||||
b = [];
|
||||
b[-1] = -1;
|
||||
b[0xffffffff] = null;
|
||||
b[0x100000000] = null;
|
||||
b[""] = 0;
|
||||
b["\xff\x7f\u7fff\uffff\ufeff\ufffe"] = 1;
|
||||
b["\ud800 \udbff \udc00 \udfff"] = 2;
|
||||
check(b);
|
||||
|
||||
// An array's .length property is not enumerable, so it is not cloned.
|
||||
b = Array(5);
|
||||
assertEq(b.length, 5);
|
||||
a = check(b);
|
||||
assertEq(a.length, 0);
|
||||
|
||||
b[1] = "ok";
|
||||
a = check(b);
|
||||
assertEq(a.length, 2);
|
||||
|
||||
// Check that prototypes are not cloned, per spec.
|
||||
b = Object.create({x:1});
|
||||
b.y = 2;
|
||||
b.z = 3;
|
||||
check(b);
|
||||
|
||||
// Check that cloning separates merge points in the tree, per spec.
|
||||
var same = {};
|
||||
b = {one: same, two: same};
|
||||
a = check(b);
|
||||
assertEq(a.one === a.two, false);
|
||||
|
||||
b = [same, same];
|
||||
a = check(b);
|
||||
assertEq(a[0] === a[1], false);
|
||||
|
||||
// Try cloning a deep object. Don't fail with "too much recursion".
|
||||
b = {};
|
||||
var current = b;
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
var next = {};
|
||||
current['x' + i] = next;
|
||||
current = next;
|
||||
}
|
||||
check(b, "deepObject"); // takes 2 seconds :-\
|
||||
|
||||
/*
|
||||
XXX TODO spin this out into its own test
|
||||
// This fails quickly with an OOM error. An exception would be nicer.
|
||||
function Infinitree() {
|
||||
return { get left() { return new Infinitree; },
|
||||
get right() { return new Infinitree; }};
|
||||
}
|
||||
var threw = false;
|
||||
try {
|
||||
serialize(new Infinitree);
|
||||
} catch (exc) {
|
||||
threw = true;
|
||||
}
|
||||
assertEq(threw, true);
|
||||
*/
|
||||
|
||||
// Clone an array with holes.
|
||||
check([0, 1, 2, , 4, 5, 6]);
|
||||
|
||||
// Array holes should not take up space.
|
||||
b = [];
|
||||
b[255] = 1;
|
||||
check(b);
|
||||
assertEq(serialize(b).length < 255, true);
|
||||
|
||||
// Self-modifying object.
|
||||
// This should never read through to b's prototype.
|
||||
b = Object.create({y: 2},
|
||||
{x: {enumerable: true,
|
||||
configurable: true,
|
||||
get: function() { if (this.hasOwnProperty("y")) delete this.y; return 1; }},
|
||||
y: {enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: 3}});
|
||||
check(b, "selfModifyingObject");
|
||||
|
||||
// Ignore properties with object-ids.
|
||||
var uri = "http://example.net";
|
||||
b = {x: 1, y: 2};
|
||||
Object.defineProperty(b, AttributeName(uri, "x"), {enumerable: true, value: 3});
|
||||
Object.defineProperty(b, QName(uri, "y"), {enumerable: true, value: 5});
|
||||
check(b);
|
||||
}
|
||||
|
||||
test();
|
||||
reportCompare(0, 0, 'ok');
|
31
js/src/tests/js1_8_5/extensions/clone-regexp.js
Normal file
31
js/src/tests/js1_8_5/extensions/clone-regexp.js
Normal file
@ -0,0 +1,31 @@
|
||||
// -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
function testRegExp(b) {
|
||||
var a = deserialize(serialize(b));
|
||||
assertEq(a === b, false);
|
||||
assertEq(Object.getPrototypeOf(a), RegExp.prototype);
|
||||
assertEq(Object.prototype.toString.call(a), "[object RegExp]");
|
||||
for (p in a)
|
||||
throw new Error("cloned RegExp should have no enumerable properties");
|
||||
|
||||
assertEq(a.source, b.source);
|
||||
assertEq(a.global, b.global);
|
||||
assertEq(a.ignoreCase, b.ignoreCase);
|
||||
assertEq(a.multiline, b.multiline);
|
||||
assertEq(a.sticky, b.sticky);
|
||||
}
|
||||
|
||||
testRegExp(RegExp(""));
|
||||
testRegExp(/(?:)/);
|
||||
testRegExp(/^(.*)$/gimy);
|
||||
testRegExp(RegExp.prototype);
|
||||
|
||||
var re = /\bx\b/gi;
|
||||
re.expando = true;
|
||||
testRegExp(re);
|
||||
re.__proto__ = {};
|
||||
testRegExp(re);
|
||||
|
||||
reportCompare(0, 0, 'ok');
|
32
js/src/tests/js1_8_5/extensions/clone-simple.js
Normal file
32
js/src/tests/js1_8_5/extensions/clone-simple.js
Normal file
@ -0,0 +1,32 @@
|
||||
// -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
function testEq(b) {
|
||||
var a = deserialize(serialize(b));
|
||||
assertEq(a, b);
|
||||
}
|
||||
|
||||
testEq(void 0);
|
||||
testEq(null);
|
||||
|
||||
testEq(true);
|
||||
testEq(false);
|
||||
|
||||
testEq(0);
|
||||
testEq(-0);
|
||||
testEq(1/0);
|
||||
testEq(-1/0);
|
||||
testEq(0/0);
|
||||
testEq(Math.PI);
|
||||
|
||||
testEq("");
|
||||
testEq("\0");
|
||||
testEq("a"); // unit string
|
||||
testEq("ab"); // length-2 string
|
||||
testEq("abc\0123\r\n"); // nested null character
|
||||
testEq("\xff\x7f\u7fff\uffff\ufeff\ufffe"); // random unicode stuff
|
||||
testEq("\ud800 \udbff \udc00 \udfff"); // busted surrogate pairs
|
||||
testEq(Array(1024).join(Array(1024).join("x"))); // 2MB string
|
||||
|
||||
reportCompare(0, 0, 'ok');
|
83
js/src/tests/js1_8_5/extensions/clone-typed-array.js
Normal file
83
js/src/tests/js1_8_5/extensions/clone-typed-array.js
Normal file
@ -0,0 +1,83 @@
|
||||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
function assertArraysEqual(a, b) {
|
||||
assertEq(a.constructor, b.constructor);
|
||||
assertEq(a.length, b.length);
|
||||
for (var i = 0; i < a.length; i++)
|
||||
assertEq(a[i], b[i]);
|
||||
}
|
||||
|
||||
function check(b) {
|
||||
var a = deserialize(serialize(b));
|
||||
assertArraysEqual(a, b);
|
||||
}
|
||||
|
||||
function checkPrototype(ctor) {
|
||||
var threw = false;
|
||||
try {
|
||||
serialize(ctor.prototype);
|
||||
throw new Error("serializing " + ctor.name + ".prototype should throw a TypeError");
|
||||
} catch (exc) {
|
||||
if (!(exc instanceof TypeError))
|
||||
throw exc;
|
||||
}
|
||||
}
|
||||
|
||||
function test() {
|
||||
// Test cloning ArrayBuffer objects.
|
||||
check(ArrayBuffer(0));
|
||||
check(ArrayBuffer(7));
|
||||
checkPrototype(ArrayBuffer);
|
||||
|
||||
// Test cloning typed array objects.
|
||||
var ctors = [
|
||||
Int8Array,
|
||||
Uint8Array,
|
||||
Int16Array,
|
||||
Uint16Array,
|
||||
Int32Array,
|
||||
Uint32Array,
|
||||
Float32Array,
|
||||
Float64Array,
|
||||
Uint8ClampedArray];
|
||||
|
||||
var b;
|
||||
for (var i = 0; i < ctors.length; i++) {
|
||||
var ctor = ctors[i];
|
||||
|
||||
// check empty array
|
||||
b = ctor(0);
|
||||
check(b);
|
||||
|
||||
// check array with some elements
|
||||
b = ctor(100);
|
||||
var v = 1;
|
||||
for (var j = 0; j < 100; j++) {
|
||||
b[j] = v;
|
||||
v *= 7;
|
||||
}
|
||||
b[99] = NaN; // check serializing NaNs too
|
||||
check(b);
|
||||
|
||||
// try the prototype
|
||||
checkPrototype(ctor);
|
||||
}
|
||||
|
||||
// Cloning should separately copy two TypedArrays backed by the same
|
||||
// ArrayBuffer. This also tests cloning TypedArrays where the arr->data
|
||||
// pointer is not 8-byte-aligned.
|
||||
|
||||
var base = Int8Array([0, 1, 2, 3]);
|
||||
b = [Int8Array(base.buffer, 0, 3), Int8Array(base.buffer, 1, 3)];
|
||||
var a = deserialize(serialize(b));
|
||||
base[1] = -1;
|
||||
a[0][2] = -2;
|
||||
assertArraysEqual(b[0], Int8Array([0, -1, 2])); // shared with base
|
||||
assertArraysEqual(b[1], Int8Array([-1, 2, 3])); // shared with base
|
||||
assertArraysEqual(a[0], Int8Array([0, 1, -2])); // not shared with base
|
||||
assertArraysEqual(a[1], Int8Array([1, 2, 3])); // not shared with base or a[0]
|
||||
}
|
||||
|
||||
test();
|
||||
reportCompare(0, 0, 'ok');
|
@ -15,3 +15,8 @@ script proxy-enumerateOwn-duplicates.js
|
||||
skip-if(!xulRuntime.shell) script reflect-parse.js
|
||||
script destructure-accessor.js
|
||||
script censor-strict-caller.js
|
||||
skip-if(!xulRuntime.shell) script clone-simple.js
|
||||
skip-if(!xulRuntime.shell) script clone-regexp.js
|
||||
skip-if(!xulRuntime.shell) script clone-object.js
|
||||
skip-if(!xulRuntime.shell) script clone-typed-array.js
|
||||
skip-if(!xulRuntime.shell) script clone-errors.js
|
||||
|
Loading…
Reference in New Issue
Block a user