Bug 595297 - Portable serialize/deserialize implementation of the HTML5 structured cloning algorithm (jsclone.cpp). r=gal.
authorJason Orendorff <jorendorff@mozilla.com>
Thu, 30 Sep 2010 19:47:10 -0500
changeset 54863 19add492f64d0fc85151a7bc3a35a0a39e15effa
parent 54862 b7a7105dc80f6eff994937560942c4b21531621b
child 54864 f8a4a5f689422a3abe4db55af3ba7ac9956980d3
push idunknown
push userunknown
push dateunknown
reviewersgal
bugs595297
milestone2.0b7pre
Bug 595297 - Portable serialize/deserialize implementation of the HTML5 structured cloning algorithm (jsclone.cpp). r=gal.
js/src/Makefile.in
js/src/js.msg
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsarray.cpp
js/src/jsclone.cpp
js/src/jsclone.h
js/src/jscntxt.h
js/src/jsinterp.cpp
js/src/jsnum.cpp
js/src/jspubtd.h
js/src/jsshell.msg
js/src/jstracer.cpp
js/src/jsvector.h
js/src/shell/js.cpp
js/src/tests/js1_8_5/extensions/clone-errors.js
js/src/tests/js1_8_5/extensions/clone-object.js
js/src/tests/js1_8_5/extensions/clone-regexp.js
js/src/tests/js1_8_5/extensions/clone-simple.js
js/src/tests/js1_8_5/extensions/clone-typed-array.js
js/src/tests/js1_8_5/extensions/jstests.list
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -116,16 +116,17 @@ DIST_INSTALL = 1
 VPATH		= $(srcdir)
 
 CPPSRCS		= \
 		jsapi.cpp \
 		jsarena.cpp \
 		jsarray.cpp \
 		jsatom.cpp \
 		jsbool.cpp \
+		jsclone.cpp \
 		jscntxt.cpp \
 		jsdate.cpp \
 		jsdbgapi.cpp \
 		jsdhash.cpp \
 		jsdtoa.cpp \
 		jsemit.cpp \
 		jsexn.cpp \
 		jsfun.cpp \
@@ -172,16 +173,17 @@ INSTALLED_HEADERS = \
 		js.msg \
 		jsapi.h \
 		jsarray.h \
 		jsarena.h \
 		jsatom.h \
 		jsbit.h \
 		jsbool.h \
 		jsclist.h \
+		jsclone.h \
 		jscntxt.h \
 		jscompat.h \
 		jsdate.h \
 		jsdbgapi.h \
 		jsdhash.h \
 		jsdtoa.h \
 		jsemit.h \
 		jsfun.h \
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -334,8 +334,11 @@ MSG_DEF(JSMSG_ACCESSOR_WRONG_ARGS,    25
 MSG_DEF(JSMSG_THROW_TYPE_ERROR,       252, 0, JSEXN_TYPEERR, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them")
 MSG_DEF(JSMSG_BAD_TOISOSTRING_PROP,   253, 0, JSEXN_TYPEERR, "toISOString property is not callable")
 MSG_DEF(JSMSG_BAD_PARSE_NODE,         254, 0, JSEXN_INTERNALERR, "bad parse node")
 MSG_DEF(JSMSG_NOT_EXPECTED_TYPE,      255, 3, JSEXN_TYPEERR, "{0}: expected {1}, got {2}")
 MSG_DEF(JSMSG_CALLER_IS_STRICT,       256, 0, JSEXN_TYPEERR, "access to strict mode caller function is censored")
 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")
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -52,16 +52,17 @@
 #include "jsclist.h"
 #include "jsdhash.h"
 #include "jsprf.h"
 #include "jsapi.h"
 #include "jsarray.h"
 #include "jsatom.h"
 #include "jsbool.h"
 #include "jsbuiltins.h"
+#include "jsclone.h"
 #include "jscntxt.h"
 #include "jsversion.h"
 #include "jsdate.h"
 #include "jsemit.h"
 #include "jsexn.h"
 #include "jsfun.h"
 #include "jsgc.h"
 #include "jsinterp.h"
@@ -5240,16 +5241,65 @@ JS_ConsumeJSONText(JSContext *cx, JSONPa
 JS_PUBLIC_API(JSBool)
 JS_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver)
 {
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, 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
  * first call to JS_NewRuntime.
  */
 #ifndef JS_C_STRINGS_ARE_UTF8
 JSBool js_CStringsAreUTF8 = JS_FALSE;
 #endif
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2839,16 +2839,122 @@ JS_BeginJSONParse(JSContext *cx, jsval *
 JS_PUBLIC_API(JSBool)
 JS_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len);
 
 JS_PUBLIC_API(JSBool)
 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.
  */
 struct JSLocaleCallbacks {
     JSLocaleToUpperCase     localeToUpperCase;
     JSLocaleToLowerCase     localeToLowerCase;
     JSLocaleCompare         localeCompare;
     JSLocaleToUnicode       localeToUnicode;
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2678,17 +2678,17 @@ array_indexOfHelper(JSContext *cx, JSBoo
     }
 
     for (;;) {
         if (!JS_CHECK_OPERATION_LIMIT(cx) ||
             !GetArrayElement(cx, obj, (jsuint)i, &hole, vp)) {
             return JS_FALSE;
         }
         if (!hole && StrictlyEqual(cx, *vp, tosearch)) {
-			vp->setNumber(i);
+            vp->setNumber(i);
             return JS_TRUE;
         }
         if (i == stop)
             goto not_found;
         i += direction;
     }
 
   not_found:
new file mode 100644
--- /dev/null
+++ b/js/src/jsclone.cpp
@@ -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;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jsclone.h
@@ -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 &in;
+
+    // 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___ */
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1473,16 +1473,19 @@ struct JSRuntime {
     uint32              debuggerMutations;
 
     /*
      * Security callbacks set on the runtime are used by each context unless
      * an override is set on the context.
      */
     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
      * thread-local storage, hence the JS_PROPERTY_TREE(cx) macro.
      */
     js::PropertyTree    propertyTree;
 
 #define JS_PROPERTY_TREE(cx) ((cx)->runtime->propertyTree)
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -3429,17 +3429,17 @@ BEGIN_CASE(JSOP_URSH)
         goto error;
     int32_t j;
     if (!ValueToECMAInt32(cx, regs.sp[-1], &j))
         goto error;
 
     u >>= (j & 31);
 
     regs.sp--;
-	regs.sp[-1].setNumber(uint32(u));
+    regs.sp[-1].setNumber(uint32(u));
 }
 END_CASE(JSOP_URSH)
 
 BEGIN_CASE(JSOP_ADD)
 {
     Value rval = regs.sp[-1];
     Value lval = regs.sp[-2];
 
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -403,17 +403,17 @@ ParseIntStringHelper(JSContext *cx, cons
 jsdouble
 ParseIntDoubleHelper(jsdouble d)
 {
     if (!JSDOUBLE_IS_FINITE(d))
         return js_NaN;
     if (d > 0)
         return floor(d);
     if (d < 0)
-    	return -floor(-d);
+        return -floor(-d);
     return 0;
 }
 
 } // namespace
 
 /* See ECMA 15.1.2.2. */
 static JSBool
 num_parseInt(JSContext *cx, uintN argc, Value *vp)
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -160,16 +160,20 @@ typedef struct JSScript          JSScrip
 typedef struct JSStackFrame      JSStackFrame;
 typedef struct JSXDRState        JSXDRState;
 typedef struct JSExceptionState  JSExceptionState;
 typedef struct JSLocaleCallbacks JSLocaleCallbacks;
 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;
 #endif
 
 /* JSClass (and js::ObjectOps where appropriate) function pointer typedefs. */
 
 /*
@@ -560,11 +564,42 @@ typedef JSObject *
 typedef enum {
     JSCOMPARTMENT_NEW, /* XXX Does it make sense to have a NEW? */
     JSCOMPARTMENT_DESTROY
 } JSCompartmentOp;
 
 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___ */
--- a/js/src/jsshell.msg
+++ b/js/src/jsshell.msg
@@ -50,9 +50,9 @@ MSG_DEF(JSSMSG_UNEXPECTED_EOF,          
 MSG_DEF(JSSMSG_DOEXP_USAGE,              6, 0, JSEXN_NONE, "usage: doexp obj id") 
 MSG_DEF(JSSMSG_SCRIPTS_ONLY,             7, 0, JSEXN_NONE, "only works on scripts") 
 MSG_DEF(JSSMSG_NOT_ENOUGH_ARGS,          8, 1, JSEXN_NONE, "{0}: not enough arguments")
 MSG_DEF(JSSMSG_TOO_MANY_ARGS,            9, 1, JSEXN_NONE, "{0}: too many arguments")
 MSG_DEF(JSSMSG_ASSERT_EQ_FAILED,        10, 2, JSEXN_NONE, "Assertion failed: got {0}, expected {1}")
 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")
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -13844,17 +13844,17 @@ TraceRecorder::denseArrayElement(Value& 
 
     /* Guard that index is within capacity. */
     guard(true, lir->ins2(LIR_ltui, idx_ins, capacity_ins), branchExit);
 
     /* Load the value and guard on its type to unbox it. */
     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);
 
     /* Don't let the hole value escape. Turn it into an undefined. */
     if (vp->isMagic()) {
         CHECK_STATUS(guardPrototypeHasNoIndexedProperties(obj, obj_ins, snapshot(MISMATCH_EXIT)));
         v_ins = INS_UNDEFINED();
--- a/js/src/jsvector.h
+++ b/js/src/jsvector.h
@@ -36,16 +36,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef jsvector_h_
 #define jsvector_h_
 
 #include "jstl.h"
+#include "jsprvtd.h"
 
 /* Silence dire "bugs in previous versions of MSVC have been fixed" warnings */
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4345)
 #endif
 
 namespace js {
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -70,16 +70,17 @@
 #include "jslock.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsparse.h"
 #include "jsreflect.h"
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jstracer.h"
+#include "jstypedarray.h"
 #include "jsxml.h"
 #include "jsperf.h"
 
 #include "prmjtime.h"
 
 #ifdef JSDEBUGGER
 #include "jsdebug.h"
 #ifdef JSDEBUGGER_JAVA_UI
@@ -4094,18 +4095,16 @@ Snarl(JSContext *cx, uintN argc, jsval *
 
     JS_ExecuteScript(cx, thisobj, script, NULL);
     JS_DestroyScript(cx, script);
 
     JS_SET_RVAL(cx, vp, JSVAL_VOID);
     return JS_TRUE;
 }
 
-
-
 JSBool
 Wrap(JSContext *cx, uintN argc, jsval *vp)
 {
     jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
     if (JSVAL_IS_PRIMITIVE(v)) {
         JS_SET_RVAL(cx, vp, v);
         return true;
     }
@@ -4115,16 +4114,59 @@ Wrap(JSContext *cx, uintN argc, jsval *v
                                        &JSWrapper::singleton);
     if (!wrapped)
         return false;
 
     JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(wrapped));
     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),
     JS_FN("revertVersion",  RevertVersion,  0,0),
     JS_FN("options",        Options,        0,0),
     JS_FN("load",           Load,           1,0),
     JS_FN("readline",       ReadLine,       0,0),
     JS_FN("print",          Print,          0,0),
@@ -4211,16 +4253,18 @@ static JSFunctionSpec shell_functions[] 
     JS_FN("snarl",          Snarl,          0,0),
     JS_FN("read",           Snarf,          0,0),
     JS_FN("compile",        Compile,        1,0),
     JS_FN("parse",          Parse,          1,0),
     JS_FN("timeout",        Timeout,        1,0),
     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
 };
 
 static const char shell_help_header[] =
 "Command                  Description\n"
 "=======                  ===========\n";
 
 static const char *const shell_help_messages[] = {
@@ -4336,17 +4380,19 @@ static const char *const shell_help_mess
 "read(filename)           Synonym for snarf",
 "compile(code)            Compiles a string to bytecode, potentially throwing",
 "parse(code)              Parses a string, potentially throwing",
 "timeout([seconds])\n"
 "  Get/Set the limit in seconds for the execution time for the current context.\n"
 "  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. */
 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(shell_help_messages) + 1 ==
                  JS_ARRAY_LENGTH(shell_functions));
 
 #ifdef DEBUG
 static void
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/clone-errors.js
@@ -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");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/clone-object.js
@@ -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');
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/clone-regexp.js
@@ -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');
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/clone-simple.js
@@ -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');
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/clone-typed-array.js
@@ -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');
--- a/js/src/tests/js1_8_5/extensions/jstests.list
+++ b/js/src/tests/js1_8_5/extensions/jstests.list
@@ -10,8 +10,13 @@ skip-if(!xulRuntime.shell) script worker
 skip-if(!xulRuntime.shell) script worker-timeout.js
 script scripted-proxies.js
 script array-length-protochange.js
 script parseInt-octal.js
 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