clone-complex-object.js (8675B)
1 // |reftest| slow skip-if(!xulRuntime.shell) 2 // -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 3 // Any copyright is dedicated to the Public Domain. 4 // http://creativecommons.org/licenses/publicdomain/ 5 6 // Set of properties on a cloned object that are legitimately non-enumerable, 7 // grouped by object type. 8 var non_enumerable = { 'Array': [ 'length' ], 9 'String': [ 'length' ] }; 10 11 // Set of properties on a cloned object that are legitimately non-configurable, 12 // grouped by object type. The property name '0' stands in for any indexed 13 // property. 14 var non_configurable = { 'String': [ 0 ] }; 15 16 // Set of properties on a cloned object that are legitimately non-writable, 17 // grouped by object type. The property name '0' stands in for any indexed 18 // property. 19 var non_writable = { 'String': [ 0 ] }; 20 21 function classOf(obj) { 22 var classString = Object.prototype.toString.call(obj); 23 var [ all, classname ] = classString.match(/\[object (\w+)/); 24 return classname; 25 } 26 27 function isIndex(p) { 28 var u = p >>> 0; 29 return ("" + u == p && u != 0xffffffff); 30 } 31 32 function notIndex(p) { 33 return !isIndex(p); 34 } 35 36 function tableContains(table, cls, prop) { 37 if (isIndex(prop)) 38 prop = 0; 39 if (cls.match(/\wArray$/)) 40 cls = "(typed array)"; 41 var exceptionalProps = table[cls] || []; 42 return exceptionalProps.indexOf(prop) != -1; 43 } 44 45 function shouldBeConfigurable(cls, prop) { 46 return !tableContains(non_configurable, cls, prop); 47 } 48 49 function shouldBeWritable(cls, prop) { 50 return !tableContains(non_writable, cls, prop); 51 } 52 53 function ownProperties(obj) { 54 return Object.getOwnPropertyNames(obj). 55 map(function (p) { return [p, Object.getOwnPropertyDescriptor(obj, p)]; }); 56 } 57 58 function isCloneable(pair) { 59 return typeof pair[0] === 'string' && pair[1].enumerable; 60 } 61 62 function compareProperties(a, b, stack, path) { 63 var ca = classOf(a); 64 65 // 'b', the original object, may have non-enumerable or XMLName properties; 66 // ignore them. 'a', the clone, should not have any non-enumerable 67 // properties (except .length, if it's an Array or String) or XMLName 68 // properties. 69 var pb = ownProperties(b).filter(isCloneable); 70 var pa = ownProperties(a); 71 for (var i = 0; i < pa.length; i++) { 72 var propname = pa[i][0]; 73 assertEq(typeof propname, "string", "clone should not have E4X properties " + path); 74 if (!pa[i][1].enumerable) { 75 if (tableContains(non_enumerable, ca, propname)) { 76 // remove it so that the comparisons below will work 77 pa.splice(i, 1); 78 i--; 79 } else { 80 throw new Error("non-enumerable clone property " + propname + " " + path); 81 } 82 } 83 } 84 85 // Check that, apart from properties whose names are array indexes, 86 // the enumerable properties appear in the same order. 87 var aNames = pa.map(function (pair) { return pair[1]; }).filter(notIndex); 88 var bNames = pa.map(function (pair) { return pair[1]; }).filter(notIndex); 89 assertEq(aNames.join(","), bNames.join(","), path); 90 91 // Check that the lists are the same when including array indexes. 92 function byName(a, b) { a = a[0]; b = b[0]; return a < b ? -1 : a === b ? 0 : 1; } 93 pa.sort(byName); 94 pb.sort(byName); 95 assertEq(pa.length, pb.length, "should see the same number of properties " + path); 96 for (var i = 0; i < pa.length; i++) { 97 var aName = pa[i][0]; 98 var bName = pb[i][0]; 99 assertEq(aName, bName, path); 100 101 var path2 = isIndex(aName) ? path + "[" + aName + "]" : path + "." + aName; 102 var da = pa[i][1]; 103 var db = pb[i][1]; 104 assertEq(da.configurable, shouldBeConfigurable(ca, aName), path2); 105 assertEq(da.writable, shouldBeWritable(ca, aName), path2); 106 assertEq("value" in da, true, path2); 107 var va = da.value; 108 var vb = b[pb[i][0]]; 109 stack.push([va, vb, path2]); 110 } 111 } 112 113 function isClone(a, b) { 114 var stack = [[a, b, 'obj']]; 115 var memory = new WeakMap(); 116 var rmemory = new WeakMap(); 117 118 while (stack.length > 0) { 119 var pair = stack.pop(); 120 var x = pair[0], y = pair[1], path = pair[2]; 121 if (typeof x !== "object" || x === null) { 122 // x is primitive. 123 assertEq(x, y, "equal primitives"); 124 } else if (x instanceof Date) { 125 assertEq(x.getTime(), y.getTime(), "equal times for cloned Dates"); 126 } else if (memory.has(x)) { 127 // x is an object we have seen before in a. 128 assertEq(y, memory.get(x), "repeated object the same"); 129 assertEq(rmemory.get(y), x, "repeated object's clone already seen"); 130 } else { 131 // x is an object we have not seen before. 132 // Check that we have not seen y before either. 133 assertEq(rmemory.has(y), false); 134 135 var xcls = classOf(x); 136 var ycls = classOf(y); 137 assertEq(xcls, ycls, "same [[Class]]"); 138 139 // clone objects should have the default prototype of the class 140 assertEq(Object.getPrototypeOf(x), this[xcls].prototype); 141 142 compareProperties(x, y, stack, path); 143 144 // Record that we have seen this pair of objects. 145 memory.set(x, y); 146 rmemory.set(y, x); 147 } 148 } 149 return true; 150 } 151 152 function check(val) { 153 var clone = deserialize(serialize(val)); 154 assertEq(isClone(val, clone), true); 155 return clone; 156 } 157 158 // Various recursive objects 159 160 // Recursive array. 161 var a = []; 162 a[0] = a; 163 check(a); 164 165 // Recursive Object. 166 var b = {}; 167 b.next = b; 168 check(b); 169 170 // Mutually recursive objects. 171 var a = []; 172 var b = {}; 173 var c = {}; 174 a[0] = b; 175 a[1] = b; 176 a[2] = b; 177 b.next = a; 178 check(a); 179 check(b); 180 181 // A date 182 check(new Date); 183 184 // A recursive object that is very large. 185 a = []; 186 b = a; 187 for (var i = 0; i < 10000; i++) { 188 b[0] = {}; 189 b[1] = []; 190 b = b[1]; 191 } 192 b[0] = {owner: a}; 193 b[1] = []; 194 check(a); 195 196 // Date objects should not be identical even if representing the same date 197 var ar = [ new Date(1000), new Date(1000) ]; 198 var clone = check(ar); 199 assertEq(clone[0] === clone[1], false); 200 201 // Identity preservation for various types of objects 202 203 function checkSimpleIdentity(v) 204 { 205 a = check([ v, v ]); 206 assertEq(a[0] === a[1], true); 207 return a; 208 } 209 210 var v = new Boolean(true); 211 checkSimpleIdentity(v); 212 213 v = new Number(17); 214 checkSimpleIdentity(v); 215 216 v = new String("yo"); 217 checkSimpleIdentity(v); 218 219 v = "fish"; 220 checkSimpleIdentity(v); 221 222 v = new Int8Array([ 10, 20 ]); 223 checkSimpleIdentity(v); 224 225 v = new ArrayBuffer(7); 226 checkSimpleIdentity(v); 227 228 v = new Date(1000); 229 b = [ v, v, { 'date': v } ]; 230 clone = check(b); 231 assertEq(clone[0] === clone[1], true); 232 assertEq(clone[0], clone[2]['date']); 233 assertEq(clone[0] === v, false); 234 235 // Reduced and modified from postMessage_structured_clone test 236 let foo = { }; 237 let baz = { }; 238 let obj = { 'foo': foo, 239 'bar': { 'foo': foo }, 240 'expando': { 'expando': baz }, 241 'baz': baz }; 242 check(obj); 243 244 for (obj of getTestContent()) 245 check(obj); 246 247 // Stolen wholesale from postMessage_structured_clone_helper.js 248 function* getTestContent() 249 { 250 yield "hello"; 251 yield 2+3; 252 yield 12; 253 yield null; 254 yield "complex" + "string"; 255 yield new Object(); 256 yield new Date(1306113544); 257 yield [1, 2, 3, 4, 5]; 258 let obj = new Object(); 259 obj.foo = 3; 260 obj.bar = "hi"; 261 obj.baz = new Date(1306113544); 262 obj.boo = obj; 263 yield obj; 264 265 let recursiveobj = new Object(); 266 recursiveobj.a = recursiveobj; 267 recursiveobj.foo = new Object(); 268 recursiveobj.foo.bar = "bar"; 269 recursiveobj.foo.backref = recursiveobj; 270 recursiveobj.foo.baz = 84; 271 recursiveobj.foo.backref2 = recursiveobj; 272 recursiveobj.bar = new Object(); 273 recursiveobj.bar.foo = "foo"; 274 recursiveobj.bar.backref = recursiveobj; 275 recursiveobj.bar.baz = new Date(1306113544); 276 recursiveobj.bar.backref2 = recursiveobj; 277 recursiveobj.expando = recursiveobj; 278 yield recursiveobj; 279 280 obj = new Object(); 281 obj.expando1 = 1; 282 obj.foo = new Object(); 283 obj.foo.bar = 2; 284 obj.bar = new Object(); 285 obj.bar.foo = obj.foo; 286 obj.expando = new Object(); 287 obj.expando.expando = new Object(); 288 obj.expando.expando.obj = obj; 289 obj.expando2 = 4; 290 obj.baz = obj.expando.expando; 291 obj.blah = obj.bar; 292 obj.foo.baz = obj.blah; 293 obj.foo.blah = obj.blah; 294 yield obj; 295 296 let diamond = new Object(); 297 obj = new Object(); 298 obj.foo = "foo"; 299 obj.bar = 92; 300 obj.backref = diamond; 301 diamond.ref1 = obj; 302 diamond.ref2 = obj; 303 yield diamond; 304 305 let doubleref = new Object(); 306 obj = new Object(); 307 doubleref.ref1 = obj; 308 doubleref.ref2 = obj; 309 yield doubleref; 310 } 311 312 reportCompare(0, 0, 'ok');