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: // Assert that cloning b does the right thing as far as we can tell. michael@0: // Caveat: getters in b must produce the same value each time they're michael@0: // called. We may call them several times. michael@0: // michael@0: // If desc is provided, then the very first thing we do to b is clone it. michael@0: // (The self-modifying object test counts on this.) michael@0: // michael@0: function check(b, desc) { michael@0: function classOf(obj) { michael@0: return Object.prototype.toString.call(obj); 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 notIndex(p) { michael@0: var u = p >>> 0; michael@0: return !("" + u == p && u != 0xffffffff); michael@0: } michael@0: michael@0: function assertIsCloneOf(a, b, path) { michael@0: assertEq(a === b, false); michael@0: michael@0: var ca = classOf(a); michael@0: assertEq(ca, classOf(b), path); michael@0: michael@0: assertEq(Object.getPrototypeOf(a), michael@0: ca == "[object Object]" ? Object.prototype : Array.prototype, michael@0: path); michael@0: michael@0: // 'b', the original object, may have non-enumerable or XMLName michael@0: // properties; ignore them. 'a', the clone, should not have any michael@0: // non-enumerable properties (except .length, if it's an Array) or michael@0: // XMLName 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: assertEq(typeof pa[i][0], "string", "clone should not have E4X properties " + path); michael@0: if (!pa[i][1].enumerable) { michael@0: if (Array.isArray(a) && pa[i][0] == "length") { 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 = path + "." + aName; michael@0: var da = pa[i][1]; michael@0: var db = pb[i][1]; michael@0: assertEq(da.configurable, true, path2); michael@0: assertEq(da.writable, true, 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: if (typeof va === "object" && va !== null) michael@0: queue.push([va, vb, path2]); michael@0: else michael@0: assertEq(va, vb, path2); michael@0: } michael@0: } michael@0: michael@0: var banner = "while testing clone of " + (desc || uneval(b)); michael@0: var a = deserialize(serialize(b)); michael@0: var queue = [[a, b, banner]]; michael@0: while (queue.length) { michael@0: var triple = queue.shift(); michael@0: assertIsCloneOf(triple[0], triple[1], triple[2]); michael@0: } michael@0: michael@0: return a; // for further testing michael@0: } michael@0: michael@0: function test() { michael@0: check({}); michael@0: check([]); michael@0: check({x: 0}); michael@0: check({x: 0.7, p: "forty-two", y: null, z: undefined}); michael@0: check(Array.prototype); michael@0: check(Object.prototype); michael@0: michael@0: // before and after michael@0: var b, a; michael@0: michael@0: // Slow array. michael@0: b = [, 1, 2, 3]; michael@0: b.expando = true; michael@0: b[5] = 5; michael@0: b[0] = 0; michael@0: b[4] = 4; michael@0: delete b[2]; michael@0: check(b); michael@0: michael@0: // Check cloning properties other than basic data properties. (check() michael@0: // asserts that the properties of the clone are configurable, writable, michael@0: // enumerable data properties.) michael@0: b = {}; michael@0: Object.defineProperties(b, { michael@0: x: {enumerable: true, get: function () { return 12479; }}, michael@0: y: {enumerable: true, configurable: true, writable: false, value: 0}, michael@0: z: {enumerable: true, configurable: false, writable: true, value: 0}, michael@0: hidden: {enumerable:false, value: 1334}}); michael@0: check(b); michael@0: michael@0: // Check corner cases involving property names. michael@0: b = {"-1": -1, michael@0: 0xffffffff: null, michael@0: 0x100000000: null, michael@0: "": 0, michael@0: "\xff\x7f\u7fff\uffff\ufeff\ufffe": 1, // random unicode id michael@0: "\ud800 \udbff \udc00 \udfff": 2}; // busted surrogate pairs michael@0: check(b); michael@0: michael@0: b = []; michael@0: b[-1] = -1; michael@0: b[0xffffffff] = null; michael@0: b[0x100000000] = null; michael@0: b[""] = 0; michael@0: b["\xff\x7f\u7fff\uffff\ufeff\ufffe"] = 1; michael@0: b["\ud800 \udbff \udc00 \udfff"] = 2; michael@0: check(b); michael@0: michael@0: // An array's .length property is not enumerable, so it is not cloned. michael@0: b = Array(5); michael@0: assertEq(b.length, 5); michael@0: a = check(b); michael@0: assertEq(a.length, 0); michael@0: michael@0: b[1] = "ok"; michael@0: a = check(b); michael@0: assertEq(a.length, 2); michael@0: michael@0: // Check that prototypes are not cloned, per spec. michael@0: b = Object.create({x:1}); michael@0: b.y = 2; michael@0: b.z = 3; michael@0: check(b); michael@0: michael@0: // Check that cloning does not separate merge points in the tree. michael@0: var same = {}; michael@0: b = {one: same, two: same}; michael@0: a = check(b); michael@0: assertEq(a.one === a.two, true); michael@0: michael@0: b = [same, same]; michael@0: a = check(b); michael@0: assertEq(a[0] === a[1], true); michael@0: michael@0: // Try cloning a deep object. Don't fail with "too much recursion". michael@0: b = {}; michael@0: var current = b; michael@0: for (var i = 0; i < 10000; i++) { michael@0: var next = {}; michael@0: current['x' + i] = next; michael@0: current = next; michael@0: } michael@0: check(b, "deepObject"); // takes 2 seconds :-\ michael@0: michael@0: /* michael@0: XXX TODO spin this out into its own test michael@0: // This fails quickly with an OOM error. An exception would be nicer. michael@0: function Infinitree() { michael@0: return { get left() { return new Infinitree; }, michael@0: get right() { return new Infinitree; }}; michael@0: } michael@0: var threw = false; michael@0: try { michael@0: serialize(new Infinitree); michael@0: } catch (exc) { michael@0: threw = true; michael@0: } michael@0: assertEq(threw, true); michael@0: */ michael@0: michael@0: // Clone an array with holes. michael@0: check([0, 1, 2, , 4, 5, 6]); michael@0: michael@0: // Array holes should not take up space. michael@0: b = []; michael@0: b[255] = 1; michael@0: check(b); michael@0: assertEq(serialize(b).clonebuffer.length < 255, true); michael@0: michael@0: // Self-modifying object. michael@0: // This should never read through to b's prototype. michael@0: b = Object.create({y: 2}, michael@0: {x: {enumerable: true, michael@0: configurable: true, michael@0: get: function() { if (this.hasOwnProperty("y")) delete this.y; return 1; }}, michael@0: y: {enumerable: true, michael@0: configurable: true, michael@0: writable: true, michael@0: value: 3}}); michael@0: check(b, "selfModifyingObject"); michael@0: michael@0: // Ignore properties with object-ids. michael@0: var uri = "http://example.net"; michael@0: b = {x: 1, y: 2}; michael@0: Object.defineProperty(b, Array(uri, "x"), {enumerable: true, value: 3}); michael@0: Object.defineProperty(b, Array(uri, "y"), {enumerable: true, value: 5}); michael@0: check(b); michael@0: } michael@0: michael@0: test(); michael@0: reportCompare(0, 0, 'ok');