Bug 789593 - Clone typed arrays by cloning their buffers and only saving construction parameters. r=jorendorff, bent
authorSteve Fink <sfink@mozilla.com>
Fri, 22 Feb 2013 13:43:28 -0800
changeset 128520 058c640a3799b7c03839d98b713aea289be03941
parent 128519 42babc8d9d827ea1f06b0e57dc3c35a6f7434682
child 128521 0a06b9069085aaa5337aac26196011aae9e1bb5f
push id26360
push usersfink@mozilla.com
push dateThu, 11 Apr 2013 23:43:59 +0000
treeherdermozilla-inbound@058c640a3799 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff, bent
bugs789593
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 789593 - Clone typed arrays by cloning their buffers and only saving construction parameters. r=jorendorff, bent Also enters a compartment where needed, and bumps both the structured clone and IndexedDB schema versions
dom/base/nsJSEnvironment.cpp
dom/indexedDB/OpenDatabaseHelper.cpp
js/src/jsapi.h
js/src/jsclone.cpp
js/src/jsclone.h
js/src/tests/js1_8_5/extensions/clone-typed-array.js
js/src/tests/js1_8_5/extensions/clone-v1-typed-array-data.dat
js/src/tests/js1_8_5/extensions/clone-v1-typed-array.js
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -3525,22 +3525,23 @@ NS_DOMWriteStructuredClone(JSContext* cx
     // Don't know what this is. Bail.
     xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR);
     return JS_FALSE;
   }
 
   // Prepare the ImageData internals.
   uint32_t width = imageData->Width();
   uint32_t height = imageData->Height();
-  JS::Value dataArray = JS::ObjectValue(*imageData->GetDataObject());
+  JSObject *dataArray = imageData->GetDataObject();
 
   // Write the internals to the stream.
+  JSAutoCompartment ac(cx, dataArray);
   return JS_WriteUint32Pair(writer, SCTAG_DOM_IMAGEDATA, 0) &&
          JS_WriteUint32Pair(writer, width, height) &&
-         JS_WriteTypedArray(writer, dataArray);
+         JS_WriteTypedArray(writer, JS::ObjectValue(*dataArray));
 }
 
 void
 NS_DOMStructuredCloneError(JSContext* cx,
                            uint32_t errorid)
 {
   // We don't currently support any extensions to structured cloning.
   xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR);
--- a/dom/indexedDB/OpenDatabaseHelper.cpp
+++ b/dom/indexedDB/OpenDatabaseHelper.cpp
@@ -27,21 +27,21 @@
 using namespace mozilla;
 USING_INDEXEDDB_NAMESPACE
 USING_QUOTA_NAMESPACE
 
 namespace {
 
 // If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major
 // schema version.
-MOZ_STATIC_ASSERT(JS_STRUCTURED_CLONE_VERSION == 1,
+MOZ_STATIC_ASSERT(JS_STRUCTURED_CLONE_VERSION == 2,
                   "Need to update the major schema version.");
 
 // Major schema version. Bump for almost everything.
-const uint32_t kMajorSchemaVersion = 13;
+const uint32_t kMajorSchemaVersion = 14;
 
 // Minor schema version. Should almost always be 0 (maybe bump on release
 // branches if we have to).
 const uint32_t kMinorSchemaVersion = 0;
 
 // The schema version we store in the SQLite database is a (signed) 32-bit
 // integer. The major version is left-shifted 4 bits so the max value is
 // 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
@@ -1345,16 +1345,27 @@ UpgradeSchemaFrom12_0To13_0(mozIStorageC
 #endif
 
   rv = aConnection->SetSchemaVersion(MakeSchemaVersion(13, 0));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
+nsresult
+UpgradeSchemaFrom13_0To14_0(mozIStorageConnection* aConnection)
+{
+  // The only change between 13 and 14 was a different structured
+  // clone format, but it's backwards-compatible.
+  nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(14, 0));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
 class VersionChangeEventsRunnable;
 
 class SetVersionHelper : public AsyncConnectionHelper,
                          public IDBTransactionListener,
                          public AcquireListener
 {
   friend class VersionChangeEventsRunnable;
 
@@ -1899,17 +1910,17 @@ OpenDatabaseHelper::CreateDatabaseConnec
       rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName);
       NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
       rv = stmt->Execute();
       NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
     }
     else  {
       // This logic needs to change next time we change the schema!
-      MOZ_STATIC_ASSERT(kSQLiteSchemaVersion == int32_t((13 << 4) + 0),
+      MOZ_STATIC_ASSERT(kSQLiteSchemaVersion == int32_t((14 << 4) + 0),
                         "Need upgrade code from schema version increase.");
 
       while (schemaVersion != kSQLiteSchemaVersion) {
         if (schemaVersion == 4) {
           rv = UpgradeSchemaFrom4To5(connection);
         }
         else if (schemaVersion == 5) {
           rv = UpgradeSchemaFrom5To6(connection);
@@ -1931,16 +1942,19 @@ OpenDatabaseHelper::CreateDatabaseConnec
           rv = UpgradeSchemaFrom10_0To11_0(connection);
         }
         else if (schemaVersion == MakeSchemaVersion(11, 0)) {
           rv = UpgradeSchemaFrom11_0To12_0(connection);
         }
         else if (schemaVersion == MakeSchemaVersion(12, 0)) {
           rv = UpgradeSchemaFrom12_0To13_0(connection, &vacuumNeeded);
         }
+        else if (schemaVersion == MakeSchemaVersion(13, 0)) {
+          rv = UpgradeSchemaFrom13_0To14_0(connection);
+        }
         else {
           NS_WARNING("Unable to open IndexedDB database, no upgrade path is "
                      "available!");
           return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
         }
         NS_ENSURE_SUCCESS(rv, rv);
 
         rv = connection->GetSchemaVersion(&schemaVersion);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4399,17 +4399,17 @@ JS_PUBLIC_API(JSBool)
 JS_ParseJSONWithReviver(JSContext *cx, const jschar *chars, uint32_t len, jsval reviver,
                         jsval *vp);
 
 /************************************************************************/
 
 /* API for the HTML5 internal structured cloning algorithm. */
 
 /* The maximum supported structured-clone serialization format version. */
-#define JS_STRUCTURED_CLONE_VERSION 1
+#define JS_STRUCTURED_CLONE_VERSION 2
 
 struct JSStructuredCloneCallbacks {
     ReadStructuredCloneOp read;
     WriteStructuredCloneOp write;
     StructuredCloneErrorOp reportError;
 };
 
 /* Note: if the *data contains transferable objects, it can be read
--- a/js/src/jsclone.cpp
+++ b/js/src/jsclone.cpp
@@ -1,14 +1,37 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/Endian.h"
+/*
+ * This file implements the structured clone algorithm of
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#safe-passing-of-structured-data
+ *
+ * The implementation differs slightly in that it uses an explicit stack, and
+ * the "memory" maps source objects to sequential integer indexes rather than
+ * directly pointing to destination objects. As a result, the order in which
+ * things are added to the memory must exactly match the order in which they
+ * are placed into 'allObjs', an analogous array of back-referenceable
+ * destination objects constructed while reading.
+ *
+ * For the most part, this is easy: simply add objects to the memory when first
+ * encountering them. But reading in a typed array requires an ArrayBuffer for
+ * construction, so objects cannot just be added to 'allObjs' in the order they
+ * are created. If they were, ArrayBuffers would come before typed arrays when
+ * in fact the typed array was added to 'memory' first.
+ *
+ * So during writing, we add objects to the memory when first encountering
+ * them. When reading a typed array, a placeholder is pushed onto allObjs until
+ * the ArrayBuffer has been read, then it is updated with the actual typed
+ * array object.
+ */
+
 #include "mozilla/FloatingPoint.h"
 
 #include "jsclone.h"
 #include "jsdate.h"
 #include "jstypedarray.h"
 
 #include "jstypedarrayinlines.h"
 
@@ -35,49 +58,43 @@ enum StructuredDataType {
     SCTAG_OBJECT_OBJECT,
     SCTAG_ARRAY_BUFFER_OBJECT,
     SCTAG_BOOLEAN_OBJECT,
     SCTAG_STRING_OBJECT,
     SCTAG_NUMBER_OBJECT,
     SCTAG_BACK_REFERENCE_OBJECT,
     SCTAG_TRANSFER_MAP_HEADER,
     SCTAG_TRANSFER_MAP,
-    SCTAG_TYPED_ARRAY_MIN = 0xFFFF0100,
-    SCTAG_TYPED_ARRAY_INT8 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_INT8,
-    SCTAG_TYPED_ARRAY_UINT8 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_UINT8,
-    SCTAG_TYPED_ARRAY_INT16 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_INT16,
-    SCTAG_TYPED_ARRAY_UINT16 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_UINT16,
-    SCTAG_TYPED_ARRAY_INT32 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_INT32,
-    SCTAG_TYPED_ARRAY_UINT32 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_UINT32,
-    SCTAG_TYPED_ARRAY_FLOAT32 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_FLOAT32,
-    SCTAG_TYPED_ARRAY_FLOAT64 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_FLOAT64,
-    SCTAG_TYPED_ARRAY_UINT8_CLAMPED = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_UINT8_CLAMPED,
-    SCTAG_TYPED_ARRAY_MAX = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_MAX - 1,
+    SCTAG_TYPED_ARRAY_OBJECT,
+    SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
+    SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + TypedArray::TYPE_INT8,
+    SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + TypedArray::TYPE_UINT8,
+    SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + TypedArray::TYPE_INT16,
+    SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + TypedArray::TYPE_UINT16,
+    SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + TypedArray::TYPE_INT32,
+    SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + TypedArray::TYPE_UINT32,
+    SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + TypedArray::TYPE_FLOAT32,
+    SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + TypedArray::TYPE_FLOAT64,
+    SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED = SCTAG_TYPED_ARRAY_V1_MIN + TypedArray::TYPE_UINT8_CLAMPED,
+    SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_MIN + TypedArray::TYPE_MAX - 1,
     SCTAG_END_OF_BUILTIN_TYPES
 };
 
 enum TransferableMapHeader {
     SCTAG_TM_NOT_MARKED = 0,
     SCTAG_TM_MARKED
 };
 
 JS_FRIEND_API(uint64_t)
 js_GetSCOffset(JSStructuredCloneWriter* writer)
 {
     JS_ASSERT(writer);
     return writer->output().count() * sizeof(uint64_t);
 }
 
-static StructuredDataType
-ArrayTypeToTag(uint32_t type)
-{
-    JS_ASSERT(type < TypedArray::TYPE_MAX);
-    return static_cast<StructuredDataType>(uint32_t(SCTAG_TYPED_ARRAY_MIN) + type);
-}
-
 JS_STATIC_ASSERT(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
 JS_STATIC_ASSERT(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX);
 JS_STATIC_ASSERT(TypedArray::TYPE_INT8 == 0);
 
 bool
 js::WriteStructuredClone(JSContext *cx, HandleValue v, uint64_t **bufp, size_t *nbytesp,
                          const JSStructuredCloneCallbacks *cb, void *cbClosure,
                          jsval transferable)
@@ -534,53 +551,53 @@ JSStructuredCloneWriter::checkStack()
         JS_ASSERT(memory.has(&objs[--j].toObject()));
 #endif
 }
 
 JS_PUBLIC_API(JSBool)
 JS_WriteTypedArray(JSStructuredCloneWriter *w, jsval v)
 {
     JS_ASSERT(v.isObject());
+    assertSameCompartment(w->context(), v);
     RootedObject obj(w->context(), &v.toObject());
 
     // If the object is a security wrapper, see if we're allowed to unwrap it.
     // If we aren't, throw.
     if (obj->isWrapper())
         obj = CheckedUnwrap(obj);
     if (!obj) {
         JS_ReportError(w->context(), "Permission denied to access object");
         return false;
     }
     return w->writeTypedArray(obj);
 }
 
+/*
+ * Write out a typed array. Note that post-v1 structured clone buffers do not
+ * perform endianness conversion on stored data, so multibyte typed arrays
+ * cannot be deserialized into a different endianness machine. Endianness
+ * conversion would prevent sharing ArrayBuffers: if you have Int8Array and
+ * Int16Array views of the same ArrayBuffer, should the data bytes be
+ * byte-swapped when writing or not? The Int8Array requires them to not be
+ * swapped; the Int16Array requires that they are.
+ */
 bool
 JSStructuredCloneWriter::writeTypedArray(HandleObject arr)
 {
-    if (!out.writePair(ArrayTypeToTag(TypedArray::type(arr)), TypedArray::length(arr)))
+    if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, TypedArray::length(arr)))
+        return false;
+    uint64_t type = TypedArray::type(arr);
+    if (!out.write(type))
         return false;
 
-    switch (TypedArray::type(arr)) {
-    case TypedArray::TYPE_INT8:
-    case TypedArray::TYPE_UINT8:
-    case TypedArray::TYPE_UINT8_CLAMPED:
-        return out.writeArray((const uint8_t *) TypedArray::viewData(arr), TypedArray::length(arr));
-    case TypedArray::TYPE_INT16:
-    case TypedArray::TYPE_UINT16:
-        return out.writeArray((const uint16_t *) TypedArray::viewData(arr), TypedArray::length(arr));
-    case TypedArray::TYPE_INT32:
-    case TypedArray::TYPE_UINT32:
-    case TypedArray::TYPE_FLOAT32:
-        return out.writeArray((const uint32_t *) TypedArray::viewData(arr), TypedArray::length(arr));
-    case TypedArray::TYPE_FLOAT64:
-        return out.writeArray((const uint64_t *) TypedArray::viewData(arr), TypedArray::length(arr));
-    default:
-        JS_NOT_REACHED("unknown TypedArray type");
+    // Write out the ArrayBuffer tag and contents
+    if (!startWrite(TypedArray::bufferValue(arr)))
         return false;
-    }
+
+    return out.write(TypedArray::byteOffset(arr));
 }
 
 bool
 JSStructuredCloneWriter::writeArrayBuffer(JSHandleObject obj)
 {
     ArrayBufferObject &buffer = obj->asArrayBuffer();
     return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, buffer.byteLength()) &&
            out.writeBytes(buffer.dataPointer(), buffer.byteLength());
@@ -813,106 +830,191 @@ JSStructuredCloneReader::readString(uint
     if (!chars.allocate(nchars) || !in.readChars(chars.get(), nchars))
         return NULL;
     JSString *str = js_NewString<CanGC>(context(), chars.get(), nchars);
     if (str)
         chars.forget();
     return str;
 }
 
+static uint32_t
+TagToV1ArrayType(uint32_t tag)
+{
+    JS_ASSERT(tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX);
+    return tag - SCTAG_TYPED_ARRAY_V1_MIN;
+}
+
 JS_PUBLIC_API(JSBool)
 JS_ReadTypedArray(JSStructuredCloneReader *r, jsval *vp)
 {
     uint32_t tag, nelems;
     if (!r->input().readPair(&tag, &nelems))
         return false;
-    JS_ASSERT(tag >= SCTAG_TYPED_ARRAY_MIN && tag <= SCTAG_TYPED_ARRAY_MAX);
-    return r->readTypedArray(tag, nelems, vp);
+    if (tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
+        return r->readTypedArray(TagToV1ArrayType(tag), nelems, vp, true);
+    } else if (tag == SCTAG_TYPED_ARRAY_OBJECT) {
+        uint64_t arrayType;
+        if (!r->input().read(&arrayType))
+            return false;
+        return r->readTypedArray(arrayType, nelems, vp);
+    } else {
+        JS_ReportErrorNumber(r->context(), js_GetErrorMessage, NULL,
+                             JSMSG_SC_BAD_SERIALIZED_DATA, "expected type array");
+        return false;
+    }
 }
 
 bool
-JSStructuredCloneReader::readTypedArray(uint32_t tag, uint32_t nelems, Value *vp)
+JSStructuredCloneReader::readTypedArray(uint32_t arrayType, uint32_t nelems, Value *vp,
+                                        bool v1Read)
 {
+    if (arrayType > TypedArray::TYPE_UINT8_CLAMPED) {
+        JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
+                             JSMSG_SC_BAD_SERIALIZED_DATA, "unhandled typed array element type");
+        return false;
+    }
+
+    // Push a placeholder onto the allObjs list to stand in for the typed array
+    uint32_t placeholderIndex = allObjs.length();
+    Value dummy = JSVAL_NULL;
+    if (!allObjs.append(dummy))
+        return false;
+
+    // Read the ArrayBuffer object and its contents (but no properties)
+    Value v;
+    uint32_t byteOffset;
+    if (v1Read) {
+        if (!readV1ArrayBuffer(arrayType, nelems, &v))
+            return false;
+        byteOffset = 0;
+    } else {
+        if (!startRead(&v))
+            return false;
+        uint64_t n;
+        if (!in.read(&n))
+            return false;
+        byteOffset = n;
+    }
+    RootedObject buffer(context(), &v.toObject());
     RootedObject obj(context(), NULL);
 
-    switch (tag) {
-      case SCTAG_TYPED_ARRAY_INT8:
-        obj = JS_NewInt8Array(context(), nelems);
+    switch (arrayType) {
+      case TypedArray::TYPE_INT8:
+        obj = JS_NewInt8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
         break;
-      case SCTAG_TYPED_ARRAY_UINT8:
-        obj = JS_NewUint8Array(context(), nelems);
+      case TypedArray::TYPE_UINT8:
+        obj = JS_NewUint8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
         break;
-      case SCTAG_TYPED_ARRAY_INT16:
-        obj = JS_NewInt16Array(context(), nelems);
+      case TypedArray::TYPE_INT16:
+        obj = JS_NewInt16ArrayWithBuffer(context(), buffer, byteOffset, nelems);
         break;
-      case SCTAG_TYPED_ARRAY_UINT16:
-        obj = JS_NewUint16Array(context(), nelems);
+      case TypedArray::TYPE_UINT16:
+        obj = JS_NewUint16ArrayWithBuffer(context(), buffer, byteOffset, nelems);
         break;
-      case SCTAG_TYPED_ARRAY_INT32:
-        obj = JS_NewInt32Array(context(), nelems);
+      case TypedArray::TYPE_INT32:
+        obj = JS_NewInt32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
         break;
-      case SCTAG_TYPED_ARRAY_UINT32:
-        obj = JS_NewUint32Array(context(), nelems);
+      case TypedArray::TYPE_UINT32:
+        obj = JS_NewUint32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
         break;
-      case SCTAG_TYPED_ARRAY_FLOAT32:
-        obj = JS_NewFloat32Array(context(), nelems);
+      case TypedArray::TYPE_FLOAT32:
+        obj = JS_NewFloat32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
         break;
-      case SCTAG_TYPED_ARRAY_FLOAT64:
-        obj = JS_NewFloat64Array(context(), nelems);
+      case TypedArray::TYPE_FLOAT64:
+        obj = JS_NewFloat64ArrayWithBuffer(context(), buffer, byteOffset, nelems);
         break;
-      case SCTAG_TYPED_ARRAY_UINT8_CLAMPED:
-        obj = JS_NewUint8ClampedArray(context(), nelems);
+      case TypedArray::TYPE_UINT8_CLAMPED:
+        obj = JS_NewUint8ClampedArrayWithBuffer(context(), buffer, byteOffset, nelems);
         break;
       default:
         JS_NOT_REACHED("unknown TypedArray type");
         return false;
     }
 
     if (!obj)
         return false;
     vp->setObject(*obj);
 
-    JS_ASSERT(TypedArray::length(obj) == nelems);
-    switch (tag) {
-      case SCTAG_TYPED_ARRAY_INT8:
-        return in.readArray((uint8_t*) JS_GetInt8ArrayData(obj), nelems);
-      case SCTAG_TYPED_ARRAY_UINT8:
-        return in.readArray(JS_GetUint8ArrayData(obj), nelems);
-      case SCTAG_TYPED_ARRAY_INT16:
-        return in.readArray((uint16_t*) JS_GetInt16ArrayData(obj), nelems);
-      case SCTAG_TYPED_ARRAY_UINT16:
-        return in.readArray(JS_GetUint16ArrayData(obj), nelems);
-      case SCTAG_TYPED_ARRAY_INT32:
-        return in.readArray((uint32_t*) JS_GetInt32ArrayData(obj), nelems);
-      case SCTAG_TYPED_ARRAY_UINT32:
-        return in.readArray(JS_GetUint32ArrayData(obj), nelems);
-      case SCTAG_TYPED_ARRAY_FLOAT32:
-        return in.readArray((uint32_t*) JS_GetFloat32ArrayData(obj), nelems);
-      case SCTAG_TYPED_ARRAY_FLOAT64:
-        return in.readArray((uint64_t*) JS_GetFloat64ArrayData(obj), nelems);
-      case SCTAG_TYPED_ARRAY_UINT8_CLAMPED:
-        return in.readArray(JS_GetUint8ClampedArrayData(obj), nelems);
-      default:
-        JS_NOT_REACHED("unknown TypedArray type");
-        return false;
-    }
+    allObjs[placeholderIndex] = *vp;
+
+    return true;
 }
 
 bool
 JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, Value *vp)
 {
     JSObject *obj = ArrayBufferObject::create(context(), nbytes);
     if (!obj)
         return false;
     vp->setObject(*obj);
     ArrayBufferObject &buffer = obj->asArrayBuffer();
     JS_ASSERT(buffer.byteLength() == nbytes);
     return in.readArray(buffer.dataPointer(), nbytes);
 }
 
+static size_t
+bytesPerTypedArrayElement(uint32_t arrayType)
+{
+    switch (arrayType) {
+      case TypedArray::TYPE_INT8:
+      case TypedArray::TYPE_UINT8:
+      case TypedArray::TYPE_UINT8_CLAMPED:
+        return sizeof(uint8_t);
+      case TypedArray::TYPE_INT16:
+      case TypedArray::TYPE_UINT16:
+        return sizeof(uint16_t);
+      case TypedArray::TYPE_INT32:
+      case TypedArray::TYPE_UINT32:
+      case TypedArray::TYPE_FLOAT32:
+        return sizeof(uint32_t);
+      case TypedArray::TYPE_FLOAT64:
+        return sizeof(uint64_t);
+      default:
+        JS_NOT_REACHED("unknown TypedArray type");
+        return 0;
+    }
+}
+
+/*
+ * Read in the data for a structured clone version 1 ArrayBuffer, performing
+ * endianness-conversion while reading.
+ */
+bool
+JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, Value *vp)
+{
+    JS_ASSERT(arrayType <= TypedArray::TYPE_UINT8_CLAMPED);
+
+    uint32_t nbytes = nelems * bytesPerTypedArrayElement(arrayType);
+    JSObject *obj = ArrayBufferObject::create(context(), nbytes);
+    if (!obj)
+        return false;
+    vp->setObject(*obj);
+    ArrayBufferObject &buffer = obj->asArrayBuffer();
+    JS_ASSERT(buffer.byteLength() == nbytes);
+
+    switch (arrayType) {
+      case TypedArray::TYPE_INT8:
+      case TypedArray::TYPE_UINT8:
+      case TypedArray::TYPE_UINT8_CLAMPED:
+        return in.readArray((uint8_t*) buffer.dataPointer(), nelems);
+      case TypedArray::TYPE_INT16:
+      case TypedArray::TYPE_UINT16:
+        return in.readArray((uint16_t*) buffer.dataPointer(), nelems);
+      case TypedArray::TYPE_INT32:
+      case TypedArray::TYPE_UINT32:
+      case TypedArray::TYPE_FLOAT32:
+        return in.readArray((uint32_t*) buffer.dataPointer(), nelems);
+      case TypedArray::TYPE_FLOAT64:
+        return in.readArray((uint64_t*) buffer.dataPointer(), nelems);
+      default:
+        JS_NOT_REACHED("unknown TypedArray type");
+        return false;
+    }
+}
+
 bool
 JSStructuredCloneReader::startRead(Value *vp)
 {
     uint32_t tag, data;
 
     if (!in.readPair(&tag, &data))
         return false;
     switch (tag) {
@@ -1030,29 +1132,37 @@ JSStructuredCloneReader::startRead(Value
                              "invalid input");
         return false;
 
       case SCTAG_ARRAY_BUFFER_OBJECT:
         if (!readArrayBuffer(data, vp))
             return false;
         break;
 
+      case SCTAG_TYPED_ARRAY_OBJECT:
+        // readTypedArray adds the array to allObjs
+        uint64_t arrayType;
+        if (!in.read(&arrayType))
+            return false;
+        return readTypedArray(arrayType, data, vp);
+        break;
+
       default: {
         if (tag <= SCTAG_FLOAT_MAX) {
             double d = ReinterpretPairAsDouble(tag, data);
             if (!checkDouble(d))
                 return false;
             vp->setNumber(d);
             break;
         }
 
-        if (SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX) {
-            if (!readTypedArray(tag, data, vp))
-                return false;
-            break;
+        if (SCTAG_TYPED_ARRAY_V1_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
+            // A v1-format typed array
+            // readTypedArray adds the array to allObjs
+            return readTypedArray(TagToV1ArrayType(tag), data, vp, true);
         }
 
         if (!callbacks || !callbacks->read) {
             JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
                                  "unsupported type");
             return false;
         }
         JSObject *obj = callbacks->read(context(), this, tag, data, closure);
--- a/js/src/jsclone.h
+++ b/js/src/jsclone.h
@@ -105,18 +105,19 @@ struct JSStructuredCloneReader {
 
   private:
     JSContext *context() { return in.context(); }
 
     bool readTransferMap();
 
     bool checkDouble(double d);
     JSString *readString(uint32_t nchars);
-    bool readTypedArray(uint32_t tag, uint32_t nelems, js::Value *vp);
+    bool readTypedArray(uint32_t arrayType, uint32_t nelems, js::Value *vp, bool v1Read = false);
     bool readArrayBuffer(uint32_t nbytes, js::Value *vp);
+    bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, 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;
 
--- a/js/src/tests/js1_8_5/extensions/clone-typed-array.js
+++ b/js/src/tests/js1_8_5/extensions/clone-typed-array.js
@@ -60,25 +60,45 @@ function test() {
         }
         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.
+    // Two TypedArrays backed by the same ArrayBuffer should be cloned into two
+    // TypedArrays still sharing a buffer. 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]
+    assertArraysEqual(a[1], Int8Array([1, -2, 3])); // not shared with base, shared with a[0]
+
+    assertEq(b[0].buffer, b[1].buffer);
+    assertEq(b[1].byteOffset, 1);
+    assertEq(b[1].byteLength, 3);
+    assertEq(b[1].buffer.byteLength, 4);
+
+    // ArrayBuffer clones do not preserve properties
+
+    base = Int8Array([0, 1, 2, 3]);
+    b = [Int8Array(base.buffer, 0, 3), Int8Array(base.buffer, 1, 3)];
+    base.buffer.prop = "yes";
+    base.buffer.loop = b[0];
+    base.buffer.loops = [ b[0], b[1] ];
+    a = deserialize(serialize(b));
+    assertEq("prop" in a[0].buffer, false);
+    assertEq("prop" in a[1].buffer, false);
+    assertEq("loop" in a[0].buffer, false);
+    assertEq("loop" in a[1].buffer, false);
+    assertEq("loops" in a[0].buffer, false);
+    assertEq("loops" in a[1].buffer, false);
 }
 
 test();
 reportCompare(0, 0, 'ok');
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/clone-v1-typed-array-data.dat
@@ -0,0 +1,32 @@
+var captured = [];
+captured[0] = new Uint8Array([0, 0, 0, 0, 9, 0, 255, 255]);
+captured[1] = new Uint8Array([7, 0, 0, 0, 9, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0]);
+captured[2] = (new TypeError("unsupported type for structured data", "js1_8_5/extensions/clone-v1-typed-array.js", 19));
+captured[3] = new Uint8Array([0, 0, 0, 0, 0, 1, 255, 255]);
+captured[4] = new Uint8Array([100, 0, 0, 0, 0, 1, 255, 255, 1, 7, 49, 87, 97, 167, 145, 247, 193, 71, 241, 151, 33, 231, 81, 55, 129, 135, 177, 216, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+captured[5] = (new TypeError("unsupported type for structured data", "js1_8_5/extensions/clone-v1-typed-array.js", 19));
+captured[6] = new Uint8Array([0, 0, 0, 0, 1, 1, 255, 255]);
+captured[7] = new Uint8Array([100, 0, 0, 0, 1, 1, 255, 255, 1, 7, 49, 87, 97, 167, 145, 247, 193, 71, 241, 151, 33, 231, 81, 55, 129, 135, 177, 216, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+captured[8] = (new TypeError("unsupported type for structured data", "js1_8_5/extensions/clone-v1-typed-array.js", 19));
+captured[9] = new Uint8Array([0, 0, 0, 0, 2, 1, 255, 255]);
+captured[10] = new Uint8Array([100, 0, 0, 0, 2, 1, 255, 255, 1, 0, 7, 0, 49, 0, 87, 1, 97, 9, 167, 65, 145, 203, 247, 144, 193, 246, 71, 191, 241, 58, 151, 156, 33, 72, 231, 248, 81, 206, 55, 164, 129, 125, 135, 110, 177, 5, 216, 39, 224, 22, 0, 160, 0, 96, 0, 160, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+captured[11] = (new TypeError("unsupported type for structured data", "js1_8_5/extensions/clone-v1-typed-array.js", 19));
+captured[12] = new Uint8Array([0, 0, 0, 0, 3, 1, 255, 255]);
+captured[13] = new Uint8Array([100, 0, 0, 0, 3, 1, 255, 255, 1, 0, 7, 0, 49, 0, 87, 1, 97, 9, 167, 65, 145, 203, 247, 144, 193, 246, 71, 191, 241, 58, 151, 156, 33, 72, 231, 248, 81, 206, 55, 164, 129, 125, 135, 110, 177, 5, 216, 39, 224, 22, 0, 160, 0, 96, 0, 160, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+captured[14] = (new TypeError("unsupported type for structured data", "js1_8_5/extensions/clone-v1-typed-array.js", 19));
+captured[15] = new Uint8Array([0, 0, 0, 0, 4, 1, 255, 255]);
+captured[16] = new Uint8Array([100, 0, 0, 0, 4, 1, 255, 255, 1, 0, 0, 0, 7, 0, 0, 0, 49, 0, 0, 0, 87, 1, 0, 0, 97, 9, 0, 0, 167, 65, 0, 0, 145, 203, 1, 0, 247, 144, 12, 0, 193, 246, 87, 0, 71, 191, 103, 2, 241, 58, 214, 16, 151, 156, 219, 117, 33, 72, 1, 57, 231, 248, 8, 143, 81, 206, 62, 233, 55, 164, 183, 96, 129, 125, 5, 165, 135, 110, 38, 131, 177, 5, 13, 150, 216, 39, 91, 26, 224, 22, 126, 184, 0, 160, 114, 11, 0, 96, 34, 80, 0, 160, 240, 48, 0, 128, 148, 86, 0, 0, 16, 94, 0, 0, 112, 146, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+captured[17] = (new TypeError("unsupported type for structured data", "js1_8_5/extensions/clone-v1-typed-array.js", 19));
+captured[18] = new Uint8Array([0, 0, 0, 0, 5, 1, 255, 255]);
+captured[19] = new Uint8Array([100, 0, 0, 0, 5, 1, 255, 255, 1, 0, 0, 0, 7, 0, 0, 0, 49, 0, 0, 0, 87, 1, 0, 0, 97, 9, 0, 0, 167, 65, 0, 0, 145, 203, 1, 0, 247, 144, 12, 0, 193, 246, 87, 0, 71, 191, 103, 2, 241, 58, 214, 16, 151, 156, 219, 117, 33, 72, 1, 57, 231, 248, 8, 143, 81, 206, 62, 233, 55, 164, 183, 96, 129, 125, 5, 165, 135, 110, 38, 131, 177, 5, 13, 150, 216, 39, 91, 26, 224, 22, 126, 184, 0, 160, 114, 11, 0, 96, 34, 80, 0, 160, 240, 48, 0, 128, 148, 86, 0, 0, 16, 94, 0, 0, 112, 146, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+captured[20] = (new TypeError("unsupported type for structured data", "js1_8_5/extensions/clone-v1-typed-array.js", 19));
+captured[21] = new Uint8Array([0, 0, 0, 0, 6, 1, 255, 255]);
+captured[22] = new Uint8Array([100, 0, 0, 0, 6, 1, 255, 255, 0, 0, 128, 63, 0, 0, 224, 64, 0, 0, 68, 66, 0, 128, 171, 67, 0, 16, 22, 69, 0, 78, 131, 70, 128, 200, 229, 71, 112, 15, 73, 73, 130, 237, 175, 74, 210, 239, 25, 76, 216, 177, 134, 77, 57, 183, 235, 78, 82, 64, 78, 80, 72, 120, 180, 81, 63, 233, 29, 83, 23, 44, 138, 84, 40, 205, 241, 85, 131, 147, 83, 87, 19, 33, 185, 88, 240, 252, 33, 90, 82, 189, 141, 91, 80, 11, 248, 92, 230, 9, 89, 94, 169, 232, 189, 95, 148, 43, 38, 97, 34, 102, 145, 98, 187, 114, 254, 99, 100, 164, 94, 101, 215, 207, 194, 102, 220, 117, 42, 104, 33, 39, 149, 105, 61, 130, 2, 107, 234, 99, 100, 108, 109, 215, 199, 109, 127, 220, 46, 111, 239, 0, 153, 112, 209, 224, 5, 114, 110, 73, 106, 115, 65, 0, 205, 116, 57, 96, 51, 118, 49, 244, 156, 119, 171, 85, 9, 121, 236, 85, 112, 122, 46, 75, 210, 123, 200, 1, 56, 125, 143, 1, 161, 126, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 128, 127, 0, 0, 192, 127]);
+captured[23] = (new TypeError("unsupported type for structured data", "js1_8_5/extensions/clone-v1-typed-array.js", 19));
+captured[24] = new Uint8Array([0, 0, 0, 0, 7, 1, 255, 255]);
+captured[25] = new Uint8Array([100, 0, 0, 0, 7, 1, 255, 255, 0, 0, 0, 0, 0, 0, 240, 63, 0, 0, 0, 0, 0, 0, 28, 64, 0, 0, 0, 0, 0, 128, 72, 64, 0, 0, 0, 0, 0, 112, 117, 64, 0, 0, 0, 0, 0, 194, 162, 64, 0, 0, 0, 0, 192, 105, 208, 64, 0, 0, 0, 0, 16, 185, 252, 64, 0, 0, 0, 0, 238, 33, 41, 65, 0, 0, 0, 64, 176, 253, 85, 65, 0, 0, 0, 56, 250, 61, 131, 65, 0, 0, 0, 241, 58, 214, 176, 65, 0, 0, 192, 37, 231, 118, 221, 65, 0, 0, 8, 65, 10, 200, 9, 66, 0, 0, 231, 248, 8, 143, 54, 66, 0, 32, 202, 217, 39, 189, 99, 66, 0, 220, 144, 222, 130, 69, 145, 66, 0, 129, 125, 5, 165, 57, 190, 66, 224, 208, 205, 100, 112, 114, 234, 66, 196, 22, 52, 88, 34, 36, 23, 67, 236, 147, 45, 13, 158, 63, 68, 67, 110, 225, 135, 75, 170, 183, 113, 67, 128, 202, 45, 4, 106, 1, 159, 67, 48, 17, 168, 195, 60, 33, 203, 67, 10, 15, 51, 43, 21, 189, 247, 67, 41, 173, 204, 133, 114, 197, 36, 68, 132, 23, 19, 53, 196, 44, 82, 68, 39, 105, 225, 92, 87, 206, 127, 68, 2, 60, 69, 113, 140, 212, 171, 68, 130, 148, 28, 227, 250, 89, 216, 68, 242, 1, 185, 134, 187, 78, 5, 69, 180, 225, 225, 21, 228, 164, 50, 69, 126, 165, 37, 147, 71, 80, 96, 69, 156, 225, 129, 65, 125, 140, 140, 69, 104, 165, 81, 153, 237, 250, 184, 69, 187, 112, 39, 230, 143, 219, 229, 69, 164, 130, 98, 233, 29, 32, 19, 70, 80, 50, 54, 44, 26, 188, 64, 70, 12, 216, 94, 205, 45, 73, 109, 70, 10, 253, 178, 19, 8, 160, 153, 70, 105, 157, 60, 17, 7, 108, 198, 70, 188, 9, 21, 47, 134, 158, 243, 70, 132, 104, 50, 105, 181, 42, 33, 71, 231, 54, 24, 120, 189, 10, 78, 71, 10, 48, 21, 201, 101, 73, 122, 71, 9, 138, 242, 15, 57, 0, 167, 71, 200, 56, 244, 237, 49, 32, 212, 71, 175, 177, 53, 176, 43, 156, 1, 72, 242, 246, 93, 116, 76, 209, 46, 72, 20, 56, 210, 229, 34, 247, 90, 72, 18, 241, 23, 137, 62, 152, 135, 72, 240, 242, 244, 183, 54, 165, 180, 72, 146, 84, 246, 224, 143, 16, 226, 72, 0, 20, 175, 201, 251, 156, 15, 73, 128, 49, 121, 80, 92, 169, 59, 73, 80, 11, 106, 198, 48, 52, 104, 73, 230, 201, 156, 173, 170, 45, 149, 73, 169, 48, 233, 87, 245, 135, 194, 73, 148, 10, 236, 172, 246, 54, 240, 73, 131, 18, 157, 174, 47, 96, 28, 74, 51, 112, 201, 184, 41, 212, 72, 74, 45, 66, 176, 129, 164, 185, 117, 74, 231, 57, 122, 241, 111, 2, 163, 74, 170, 242, 74, 243, 33, 162, 208, 74, 170, 40, 195, 105, 187, 27, 253, 74, 149, 195, 138, 252, 67, 120, 41, 75, 34, 107, 249, 124, 59, 73, 86, 75, 190, 61, 90, 13, 20, 128, 131, 75, 6, 246, 174, 139, 17, 16, 177, 75, 138, 46, 114, 180, 30, 220, 221, 75, 185, 232, 227, 221, 154, 32, 10, 76, 162, 107, 39, 130, 135, 220, 54, 76, 46, 126, 226, 145, 246, 0, 100, 76, 104, 46, 166, 191, 215, 128, 145, 76, 54, 209, 98, 143, 121, 161, 190, 76, 15, 119, 118, 93, 74, 205, 234, 76, 45, 168, 199, 17, 161, 115, 23, 77, 39, 179, 142, 239, 44, 133, 68, 77, 194, 220, 156, 81, 135, 244, 113, 77, 84, 130, 210, 206, 236, 107, 159, 77, 10, 50, 248, 52, 111, 126, 203, 77, 201, 43, 89, 78, 161, 14, 248, 77, 80, 6, 142, 36, 205, 12, 37, 78, 134, 69, 252, 127, 51, 107, 82, 78, 213, 188, 252, 15, 205, 29, 128, 78, 117, 74, 250, 219, 38, 52, 172, 78, 38, 1, 123, 0, 162, 173, 216, 78, 1, 161, 107, 192, 237, 151, 5, 79, 225, 44, 94, 8, 240, 228, 50, 79, 69, 103, 82, 7, 82, 136, 96, 79, 185, 52, 208, 140, 143, 238, 140, 79, 34, 46, 54, 155, 189, 80, 185, 79, 94, 104, 207, 231, 165, 38, 230, 79, 82, 123, 213, 42, 209, 97, 19, 80, 232, 203, 122, 5, 151, 245, 64, 80, 214, 228, 150, 73, 200, 173, 109, 80, 59, 8, 100, 64, 15, 248, 153, 80, 52, 135, 87, 88, 13, 185, 198, 80, 78, 150, 76, 173, 235, 225, 243, 80, 132, 3, 163, 55, 174, 101, 33, 81, 0, 0, 0, 0, 0, 0, 248, 127]);
+captured[26] = (new TypeError("unsupported type for structured data", "js1_8_5/extensions/clone-v1-typed-array.js", 19));
+captured[27] = new Uint8Array([0, 0, 0, 0, 8, 1, 255, 255]);
+captured[28] = new Uint8Array([100, 0, 0, 0, 8, 1, 255, 255, 1, 7, 49, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0]);
+captured[29] = (new TypeError("unsupported type for structured data", "js1_8_5/extensions/clone-v1-typed-array.js", 19));
+captured[30] = new Uint8Array([0, 0, 0, 0, 7, 0, 255, 255, 0, 0, 0, 0, 3, 0, 255, 255, 3, 0, 0, 0, 0, 1, 255, 255, 0, 1, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 255, 255, 3, 0, 0, 0, 0, 1, 255, 255, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255]);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/clone-v1-typed-array.js
@@ -0,0 +1,128 @@
+// |reftest| skip-if(!xulRuntime.shell)
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+// This file is a copy of clone-typed-array.js from before v2 structured clone
+// was implemented. If you run this test under a v1-writing engine with the
+// environment variable JS_RECORD_RESULTS set, then it will output a log of
+// structured clone buffers resulting from running this test. You can then use
+// that log as input to another run of this same test on a newer engine, to
+// verify that older-format structured clone data can be deserialized properly.
+
+var old_serialize = serialize;
+var captured = [];
+
+if ("JS_RECORD_RESULTS" in environment) {
+  serialize = function(o) {
+    var data;
+    try {
+      data = old_serialize(o);
+      captured.push(data);
+      return data;
+    } catch(e) {
+      captured.push(e);
+      throw(e);
+    }
+  };
+} else {
+  loadRelativeToScript("clone-v1-typed-array-data.dat");
+  serialize = function(d) {
+    var data = captured.shift();
+    if (data instanceof Error)
+      throw(data);
+    else
+      return data;
+  };
+}
+
+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');
+
+if ("JS_RECORD_RESULTS" in environment) {
+  print("var captured = [];");
+  for (var i in captured) {
+    var s = "captured[" + i + "] = ";
+    if (captured[i] instanceof Error)
+      print(s + captured[i].toSource() + ";");
+    else
+      print(s + "new Uint8Array(" + [...captured[i]].toSource() + ");");
+  }
+}