mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 748309 - Test structured clone handling of cyclic graphs. r=jorendorff
This commit is contained in:
parent
71bef6e978
commit
623d790c0d
@ -3,54 +3,144 @@
|
||||
// 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 " + uneval(pa[i][0]) + " " + 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]];
|
||||
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];
|
||||
var x = pair[0], y = pair[1], path = pair[2];
|
||||
if (typeof x !== "object" || x === null) {
|
||||
// x is primitive.
|
||||
if (x !== y)
|
||||
return false;
|
||||
assertEq(x, y, "equal primitives");
|
||||
} else if (x instanceof Date) {
|
||||
if (x.getTime() !== y.getTime())
|
||||
return false;
|
||||
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.
|
||||
if (y !== memory.get(x))
|
||||
return false;
|
||||
assertEq(rmemory.get(y), x);
|
||||
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.
|
||||
if (rmemory.has(y))
|
||||
return false;
|
||||
// Check that we have not seen y before either.
|
||||
assertEq(rmemory.has(y), false);
|
||||
|
||||
// x and y must be of the same [[Class]].
|
||||
var xcls = Object.prototype.toString.call(x);
|
||||
var ycls = Object.prototype.toString.call(y);
|
||||
if (xcls !== ycls)
|
||||
return false;
|
||||
var xcls = classOf(x);
|
||||
var ycls = classOf(y);
|
||||
assertEq(xcls, ycls, "same [[Class]]");
|
||||
|
||||
// This function is only designed to check Objects and Arrays.
|
||||
assertEq(xcls === "[object Object]" || xcls === "[object Array]",
|
||||
true);
|
||||
// clone objects should have the default prototype of the class
|
||||
assertEq(Object.getPrototypeOf(x), this[xcls].prototype);
|
||||
|
||||
// Compare objects.
|
||||
var xk = Object.keys(x), yk = Object.keys(y);
|
||||
if (xk.length !== yk.length)
|
||||
return false;
|
||||
for (var i = 0; i < xk.length; i++) {
|
||||
// We must see the same property names in the same order.
|
||||
if (xk[i] !== yk[i])
|
||||
return false;
|
||||
|
||||
// Put the property values on the stack to compare later.
|
||||
stack.push([x[xk[i]], y[yk[i]]]);
|
||||
}
|
||||
compareProperties(x, y, stack, path);
|
||||
|
||||
// Record that we have seen this pair of objects.
|
||||
memory.set(x, y);
|
||||
@ -60,13 +150,14 @@ function isClone(a, b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function check(a) {
|
||||
assertEq(isClone(a, deserialize(serialize(a))), true);
|
||||
function check(val) {
|
||||
var clone = deserialize(serialize(val));
|
||||
assertEq(isClone(val, clone), true);
|
||||
return clone;
|
||||
}
|
||||
|
||||
// Various recursive objects, i.e. those which the structured cloning
|
||||
// algorithm wants us to reject due to "memory".
|
||||
//
|
||||
// Various recursive objects
|
||||
|
||||
// Recursive array.
|
||||
var a = [];
|
||||
a[0] = a;
|
||||
@ -103,4 +194,120 @@ 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 (var obj of new 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;
|
||||
|
||||
let 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();
|
||||
let obj = new Object();
|
||||
obj.foo = "foo";
|
||||
obj.bar = 92;
|
||||
obj.backref = diamond;
|
||||
diamond.ref1 = obj;
|
||||
diamond.ref2 = obj;
|
||||
yield diamond;
|
||||
|
||||
let doubleref = new Object();
|
||||
let obj = new Object();
|
||||
doubleref.ref1 = obj;
|
||||
doubleref.ref2 = obj;
|
||||
yield doubleref;
|
||||
}
|
||||
|
||||
reportCompare(0, 0, 'ok');
|
||||
|
Loading…
Reference in New Issue
Block a user