summaryrefslogtreecommitdiffstats
path: root/js/src/tests/non262/extensions/clone-complex-object.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/non262/extensions/clone-complex-object.js')
-rw-r--r--js/src/tests/non262/extensions/clone-complex-object.js313
1 files changed, 313 insertions, 0 deletions
diff --git a/js/src/tests/non262/extensions/clone-complex-object.js b/js/src/tests/non262/extensions/clone-complex-object.js
new file mode 100644
index 0000000000..869a7d805f
--- /dev/null
+++ b/js/src/tests/non262/extensions/clone-complex-object.js
@@ -0,0 +1,313 @@
+// |reftest| slow skip-if(!xulRuntime.shell)
+// -*- 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/
+
+// Set of properties on a cloned object that are legitimately non-enumerable,
+// grouped by object type.
+var non_enumerable = { 'Array': [ 'length' ],
+ 'String': [ 'length' ] };
+
+// Set of properties on a cloned object that are legitimately non-configurable,
+// grouped by object type. The property name '0' stands in for any indexed
+// property.
+var non_configurable = { 'String': [ 0 ],
+ '(typed array)': [ 0 ] };
+
+// Set of properties on a cloned object that are legitimately non-writable,
+// grouped by object type. The property name '0' stands in for any indexed
+// property.
+var non_writable = { 'String': [ 0 ] };
+
+function classOf(obj) {
+ var classString = Object.prototype.toString.call(obj);
+ var [ all, classname ] = classString.match(/\[object (\w+)/);
+ return classname;
+}
+
+function isIndex(p) {
+ var u = p >>> 0;
+ return ("" + u == p && u != 0xffffffff);
+}
+
+function notIndex(p) {
+ return !isIndex(p);
+}
+
+function tableContains(table, cls, prop) {
+ if (isIndex(prop))
+ prop = 0;
+ if (cls.match(/\wArray$/))
+ cls = "(typed array)";
+ var exceptionalProps = table[cls] || [];
+ return exceptionalProps.indexOf(prop) != -1;
+}
+
+function shouldBeConfigurable(cls, prop) {
+ return !tableContains(non_configurable, cls, prop);
+}
+
+function shouldBeWritable(cls, prop) {
+ return !tableContains(non_writable, cls, prop);
+}
+
+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 compareProperties(a, b, stack, path) {
+ var ca = classOf(a);
+
+ // '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 String) or XMLName
+ // properties.
+ var pb = ownProperties(b).filter(isCloneable);
+ var pa = ownProperties(a);
+ for (var i = 0; i < pa.length; i++) {
+ var propname = pa[i][0];
+ assertEq(typeof propname, "string", "clone should not have E4X properties " + path);
+ if (!pa[i][1].enumerable) {
+ if (tableContains(non_enumerable, ca, propname)) {
+ // remove it so that the comparisons below will work
+ pa.splice(i, 1);
+ i--;
+ } else {
+ throw new Error("non-enumerable clone property " + propname + " " + 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 = isIndex(aName) ? path + "[" + aName + "]" : path + "." + aName;
+ var da = pa[i][1];
+ var db = pb[i][1];
+ assertEq(da.configurable, shouldBeConfigurable(ca, aName), path2);
+ assertEq(da.writable, shouldBeWritable(ca, aName), path2);
+ assertEq("value" in da, true, path2);
+ var va = da.value;
+ var vb = b[pb[i][0]];
+ stack.push([va, vb, path2]);
+ }
+}
+
+function isClone(a, b) {
+ var stack = [[a, b, 'obj']];
+ var memory = new WeakMap();
+ var rmemory = new WeakMap();
+
+ while (stack.length > 0) {
+ var pair = stack.pop();
+ var x = pair[0], y = pair[1], path = pair[2];
+ if (typeof x !== "object" || x === null) {
+ // x is primitive.
+ assertEq(x, y, "equal primitives");
+ } else if (x instanceof Date) {
+ assertEq(x.getTime(), y.getTime(), "equal times for cloned Dates");
+ } else if (memory.has(x)) {
+ // x is an object we have seen before in a.
+ assertEq(y, memory.get(x), "repeated object the same");
+ assertEq(rmemory.get(y), x, "repeated object's clone already seen");
+ } else {
+ // x is an object we have not seen before.
+ // Check that we have not seen y before either.
+ assertEq(rmemory.has(y), false);
+
+ var xcls = classOf(x);
+ var ycls = classOf(y);
+ assertEq(xcls, ycls, "same [[Class]]");
+
+ // clone objects should have the default prototype of the class
+ assertEq(Object.getPrototypeOf(x), this[xcls].prototype);
+
+ compareProperties(x, y, stack, path);
+
+ // Record that we have seen this pair of objects.
+ memory.set(x, y);
+ rmemory.set(y, x);
+ }
+ }
+ return true;
+}
+
+function check(val) {
+ var clone = deserialize(serialize(val));
+ assertEq(isClone(val, clone), true);
+ return clone;
+}
+
+// Various recursive objects
+
+// Recursive array.
+var a = [];
+a[0] = a;
+check(a);
+
+// Recursive Object.
+var b = {};
+b.next = b;
+check(b);
+
+// Mutually recursive objects.
+var a = [];
+var b = {};
+var c = {};
+a[0] = b;
+a[1] = b;
+a[2] = b;
+b.next = a;
+check(a);
+check(b);
+
+// A date
+check(new Date);
+
+// A recursive object that is very large.
+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);
+
+// Date objects should not be identical even if representing the same date
+var ar = [ new Date(1000), new Date(1000) ];
+var clone = check(ar);
+assertEq(clone[0] === clone[1], false);
+
+// Identity preservation for various types of objects
+
+function checkSimpleIdentity(v)
+{
+ a = check([ v, v ]);
+ assertEq(a[0] === a[1], true);
+ return a;
+}
+
+var v = new Boolean(true);
+checkSimpleIdentity(v);
+
+v = new Number(17);
+checkSimpleIdentity(v);
+
+v = new String("yo");
+checkSimpleIdentity(v);
+
+v = "fish";
+checkSimpleIdentity(v);
+
+v = new Int8Array([ 10, 20 ]);
+checkSimpleIdentity(v);
+
+v = new ArrayBuffer(7);
+checkSimpleIdentity(v);
+
+v = new Date(1000);
+b = [ v, v, { 'date': v } ];
+clone = check(b);
+assertEq(clone[0] === clone[1], true);
+assertEq(clone[0], clone[2]['date']);
+assertEq(clone[0] === v, false);
+
+// Reduced and modified from postMessage_structured_clone test
+let foo = { };
+let baz = { };
+let obj = { 'foo': foo,
+ 'bar': { 'foo': foo },
+ 'expando': { 'expando': baz },
+ 'baz': baz };
+check(obj);
+
+for (obj of getTestContent())
+ check(obj);
+
+// Stolen wholesale from postMessage_structured_clone_helper.js
+function* getTestContent()
+{
+ yield "hello";
+ yield 2+3;
+ yield 12;
+ yield null;
+ yield "complex" + "string";
+ yield new Object();
+ yield new Date(1306113544);
+ yield [1, 2, 3, 4, 5];
+ let obj = new Object();
+ obj.foo = 3;
+ obj.bar = "hi";
+ obj.baz = new Date(1306113544);
+ obj.boo = obj;
+ yield obj;
+
+ let recursiveobj = new Object();
+ recursiveobj.a = recursiveobj;
+ recursiveobj.foo = new Object();
+ recursiveobj.foo.bar = "bar";
+ recursiveobj.foo.backref = recursiveobj;
+ recursiveobj.foo.baz = 84;
+ recursiveobj.foo.backref2 = recursiveobj;
+ recursiveobj.bar = new Object();
+ recursiveobj.bar.foo = "foo";
+ recursiveobj.bar.backref = recursiveobj;
+ recursiveobj.bar.baz = new Date(1306113544);
+ recursiveobj.bar.backref2 = recursiveobj;
+ recursiveobj.expando = recursiveobj;
+ yield recursiveobj;
+
+ obj = new Object();
+ obj.expando1 = 1;
+ obj.foo = new Object();
+ obj.foo.bar = 2;
+ obj.bar = new Object();
+ obj.bar.foo = obj.foo;
+ obj.expando = new Object();
+ obj.expando.expando = new Object();
+ obj.expando.expando.obj = obj;
+ obj.expando2 = 4;
+ obj.baz = obj.expando.expando;
+ obj.blah = obj.bar;
+ obj.foo.baz = obj.blah;
+ obj.foo.blah = obj.blah;
+ yield obj;
+
+ let diamond = new Object();
+ obj = new Object();
+ obj.foo = "foo";
+ obj.bar = 92;
+ obj.backref = diamond;
+ diamond.ref1 = obj;
+ diamond.ref2 = obj;
+ yield diamond;
+
+ let doubleref = new Object();
+ obj = new Object();
+ doubleref.ref1 = obj;
+ doubleref.ref2 = obj;
+ yield doubleref;
+}
+
+reportCompare(0, 0, 'ok');