michael@0: // |reftest| skip-if(!xulRuntime.shell) michael@0: // -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: // Any copyright is dedicated to the Public Domain. michael@0: // http://creativecommons.org/licenses/publicdomain/ michael@0: michael@0: // Set of properties on a cloned object that are legitimately non-enumerable, michael@0: // grouped by object type. michael@0: var non_enumerable = { 'Array': [ 'length' ], michael@0: 'String': [ 'length' ] }; michael@0: michael@0: // Set of properties on a cloned object that are legitimately non-configurable, michael@0: // grouped by object type. The property name '0' stands in for any indexed michael@0: // property. michael@0: var non_configurable = { 'String': [ 0 ], michael@0: '(typed array)': [ 0 ] }; michael@0: michael@0: // Set of properties on a cloned object that are legitimately non-writable, michael@0: // grouped by object type. The property name '0' stands in for any indexed michael@0: // property. michael@0: var non_writable = { 'String': [ 0 ] }; michael@0: michael@0: function classOf(obj) { michael@0: var classString = Object.prototype.toString.call(obj); michael@0: var [ all, classname ] = classString.match(/\[object (\w+)/); michael@0: return classname; michael@0: } michael@0: michael@0: function isIndex(p) { michael@0: var u = p >>> 0; michael@0: return ("" + u == p && u != 0xffffffff); michael@0: } michael@0: michael@0: function notIndex(p) { michael@0: return !isIndex(p); michael@0: } michael@0: michael@0: function tableContains(table, cls, prop) { michael@0: if (isIndex(prop)) michael@0: prop = 0; michael@0: if (cls.match(/\wArray$/)) michael@0: cls = "(typed array)"; michael@0: var exceptionalProps = table[cls] || []; michael@0: return exceptionalProps.indexOf(prop) != -1; michael@0: } michael@0: michael@0: function shouldBeConfigurable(cls, prop) { michael@0: return !tableContains(non_configurable, cls, prop); michael@0: } michael@0: michael@0: function shouldBeWritable(cls, prop) { michael@0: return !tableContains(non_writable, cls, prop); michael@0: } michael@0: michael@0: function ownProperties(obj) { michael@0: return Object.getOwnPropertyNames(obj). michael@0: map(function (p) { return [p, Object.getOwnPropertyDescriptor(obj, p)]; }); michael@0: } michael@0: michael@0: function isCloneable(pair) { michael@0: return typeof pair[0] === 'string' && pair[1].enumerable; michael@0: } michael@0: michael@0: function compareProperties(a, b, stack, path) { michael@0: var ca = classOf(a); michael@0: michael@0: // 'b', the original object, may have non-enumerable or XMLName properties; michael@0: // ignore them. 'a', the clone, should not have any non-enumerable michael@0: // properties (except .length, if it's an Array or String) or XMLName michael@0: // properties. michael@0: var pb = ownProperties(b).filter(isCloneable); michael@0: var pa = ownProperties(a); michael@0: for (var i = 0; i < pa.length; i++) { michael@0: var propname = pa[i][0]; michael@0: assertEq(typeof propname, "string", "clone should not have E4X properties " + path); michael@0: if (!pa[i][1].enumerable) { michael@0: if (tableContains(non_enumerable, ca, propname)) { michael@0: // remove it so that the comparisons below will work michael@0: pa.splice(i, 1); michael@0: i--; michael@0: } else { michael@0: throw new Error("non-enumerable clone property " + uneval(pa[i][0]) + " " + path); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Check that, apart from properties whose names are array indexes, michael@0: // the enumerable properties appear in the same order. michael@0: var aNames = pa.map(function (pair) { return pair[1]; }).filter(notIndex); michael@0: var bNames = pa.map(function (pair) { return pair[1]; }).filter(notIndex); michael@0: assertEq(aNames.join(","), bNames.join(","), path); michael@0: michael@0: // Check that the lists are the same when including array indexes. michael@0: function byName(a, b) { a = a[0]; b = b[0]; return a < b ? -1 : a === b ? 0 : 1; } michael@0: pa.sort(byName); michael@0: pb.sort(byName); michael@0: assertEq(pa.length, pb.length, "should see the same number of properties " + path); michael@0: for (var i = 0; i < pa.length; i++) { michael@0: var aName = pa[i][0]; michael@0: var bName = pb[i][0]; michael@0: assertEq(aName, bName, path); michael@0: michael@0: var path2 = isIndex(aName) ? path + "[" + aName + "]" : path + "." + aName; michael@0: var da = pa[i][1]; michael@0: var db = pb[i][1]; michael@0: assertEq(da.configurable, shouldBeConfigurable(ca, aName), path2); michael@0: assertEq(da.writable, shouldBeWritable(ca, aName), path2); michael@0: assertEq("value" in da, true, path2); michael@0: var va = da.value; michael@0: var vb = b[pb[i][0]]; michael@0: stack.push([va, vb, path2]); michael@0: } michael@0: } michael@0: michael@0: function isClone(a, b) { michael@0: var stack = [[a, b, 'obj']]; michael@0: var memory = new WeakMap(); michael@0: var rmemory = new WeakMap(); michael@0: michael@0: while (stack.length > 0) { michael@0: var pair = stack.pop(); michael@0: var x = pair[0], y = pair[1], path = pair[2]; michael@0: if (typeof x !== "object" || x === null) { michael@0: // x is primitive. michael@0: assertEq(x, y, "equal primitives"); michael@0: } else if (x instanceof Date) { michael@0: assertEq(x.getTime(), y.getTime(), "equal times for cloned Dates"); michael@0: } else if (memory.has(x)) { michael@0: // x is an object we have seen before in a. michael@0: assertEq(y, memory.get(x), "repeated object the same"); michael@0: assertEq(rmemory.get(y), x, "repeated object's clone already seen"); michael@0: } else { michael@0: // x is an object we have not seen before. michael@0: // Check that we have not seen y before either. michael@0: assertEq(rmemory.has(y), false); michael@0: michael@0: var xcls = classOf(x); michael@0: var ycls = classOf(y); michael@0: assertEq(xcls, ycls, "same [[Class]]"); michael@0: michael@0: // clone objects should have the default prototype of the class michael@0: assertEq(Object.getPrototypeOf(x), this[xcls].prototype); michael@0: michael@0: compareProperties(x, y, stack, path); michael@0: michael@0: // Record that we have seen this pair of objects. michael@0: memory.set(x, y); michael@0: rmemory.set(y, x); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: function check(val) { michael@0: var clone = deserialize(serialize(val)); michael@0: assertEq(isClone(val, clone), true); michael@0: return clone; michael@0: } michael@0: michael@0: // Various recursive objects michael@0: michael@0: // Recursive array. michael@0: var a = []; michael@0: a[0] = a; michael@0: check(a); michael@0: michael@0: // Recursive Object. michael@0: var b = {}; michael@0: b.next = b; michael@0: check(b); michael@0: michael@0: // Mutually recursive objects. michael@0: var a = []; michael@0: var b = {}; michael@0: var c = {}; michael@0: a[0] = b; michael@0: a[1] = b; michael@0: a[2] = b; michael@0: b.next = a; michael@0: check(a); michael@0: check(b); michael@0: michael@0: // A date michael@0: check(new Date); michael@0: michael@0: // A recursive object that is very large. michael@0: a = []; michael@0: b = a; michael@0: for (var i = 0; i < 10000; i++) { michael@0: b[0] = {}; michael@0: b[1] = []; michael@0: b = b[1]; michael@0: } michael@0: b[0] = {owner: a}; michael@0: b[1] = []; michael@0: check(a); michael@0: michael@0: // Date objects should not be identical even if representing the same date michael@0: var ar = [ new Date(1000), new Date(1000) ]; michael@0: var clone = check(ar); michael@0: assertEq(clone[0] === clone[1], false); michael@0: michael@0: // Identity preservation for various types of objects michael@0: michael@0: function checkSimpleIdentity(v) michael@0: { michael@0: a = check([ v, v ]); michael@0: assertEq(a[0] === a[1], true); michael@0: return a; michael@0: } michael@0: michael@0: var v = new Boolean(true); michael@0: checkSimpleIdentity(v); michael@0: michael@0: v = new Number(17); michael@0: checkSimpleIdentity(v); michael@0: michael@0: v = new String("yo"); michael@0: checkSimpleIdentity(v); michael@0: michael@0: v = "fish"; michael@0: checkSimpleIdentity(v); michael@0: michael@0: v = new Int8Array([ 10, 20 ]); michael@0: checkSimpleIdentity(v); michael@0: michael@0: v = new ArrayBuffer(7); michael@0: checkSimpleIdentity(v); michael@0: michael@0: v = new Date(1000); michael@0: b = [ v, v, { 'date': v } ]; michael@0: clone = check(b); michael@0: assertEq(clone[0] === clone[1], true); michael@0: assertEq(clone[0], clone[2]['date']); michael@0: assertEq(clone[0] === v, false); michael@0: michael@0: // Reduced and modified from postMessage_structured_clone test michael@0: let foo = { }; michael@0: let baz = { }; michael@0: let obj = { 'foo': foo, michael@0: 'bar': { 'foo': foo }, michael@0: 'expando': { 'expando': baz }, michael@0: 'baz': baz }; michael@0: check(obj); michael@0: michael@0: for (var obj of new getTestContent) michael@0: check(obj); michael@0: michael@0: // Stolen wholesale from postMessage_structured_clone_helper.js michael@0: function getTestContent() michael@0: { michael@0: yield "hello"; michael@0: yield 2+3; michael@0: yield 12; michael@0: yield null; michael@0: yield "complex" + "string"; michael@0: yield new Object(); michael@0: yield new Date(1306113544); michael@0: yield [1, 2, 3, 4, 5]; michael@0: let obj = new Object(); michael@0: obj.foo = 3; michael@0: obj.bar = "hi"; michael@0: obj.baz = new Date(1306113544); michael@0: obj.boo = obj; michael@0: yield obj; michael@0: michael@0: let recursiveobj = new Object(); michael@0: recursiveobj.a = recursiveobj; michael@0: recursiveobj.foo = new Object(); michael@0: recursiveobj.foo.bar = "bar"; michael@0: recursiveobj.foo.backref = recursiveobj; michael@0: recursiveobj.foo.baz = 84; michael@0: recursiveobj.foo.backref2 = recursiveobj; michael@0: recursiveobj.bar = new Object(); michael@0: recursiveobj.bar.foo = "foo"; michael@0: recursiveobj.bar.backref = recursiveobj; michael@0: recursiveobj.bar.baz = new Date(1306113544); michael@0: recursiveobj.bar.backref2 = recursiveobj; michael@0: recursiveobj.expando = recursiveobj; michael@0: yield recursiveobj; michael@0: michael@0: let obj = new Object(); michael@0: obj.expando1 = 1; michael@0: obj.foo = new Object(); michael@0: obj.foo.bar = 2; michael@0: obj.bar = new Object(); michael@0: obj.bar.foo = obj.foo; michael@0: obj.expando = new Object(); michael@0: obj.expando.expando = new Object(); michael@0: obj.expando.expando.obj = obj; michael@0: obj.expando2 = 4; michael@0: obj.baz = obj.expando.expando; michael@0: obj.blah = obj.bar; michael@0: obj.foo.baz = obj.blah; michael@0: obj.foo.blah = obj.blah; michael@0: yield obj; michael@0: michael@0: let diamond = new Object(); michael@0: let obj = new Object(); michael@0: obj.foo = "foo"; michael@0: obj.bar = 92; michael@0: obj.backref = diamond; michael@0: diamond.ref1 = obj; michael@0: diamond.ref2 = obj; michael@0: yield diamond; michael@0: michael@0: let doubleref = new Object(); michael@0: let obj = new Object(); michael@0: doubleref.ref1 = obj; michael@0: doubleref.ref2 = obj; michael@0: yield doubleref; michael@0: } michael@0: michael@0: reportCompare(0, 0, 'ok');