| <html> |
| <head></head> |
| <body> |
| <div id="description"></div> |
| <input type="file" id="fileInput" multiple></input> |
| <div id="console"></div> |
| <script src="resources/postmessage-test.js"></script> |
| <script> |
| document.getElementById("description").innerHTML = "Tests that we clone object hierarchies"; |
| |
| globalThis.heapZero = 100000000000000000000000000000000000000000n - 100000000000000000000000000000000000000000n; |
| tryPostMessage('null'); |
| tryPostMessage('undefined'); |
| tryPostMessage('1'); |
| tryPostMessage('true'); |
| tryPostMessage('"1"'); |
| tryPostMessage('0n'); |
| tryPostMessage('globalThis.heapZero'); |
| tryPostMessage('-20n'); |
| tryPostMessage('4294967295n'); |
| tryPostMessage('-4294967295n'); |
| tryPostMessage('2147483647n'); |
| tryPostMessage('2147483648n'); |
| tryPostMessage('-2147483648n'); |
| tryPostMessage('-2147483649n'); |
| tryPostMessage('68719476735n'); |
| tryPostMessage('-68719476735n'); |
| tryPostMessage('1000000000000000000000000000000000n'); |
| tryPostMessage('-1000000000000000000000000000000000n'); |
| tryPostMessage('1461501637330902918203684832716283019655932542975n'); |
| tryPostMessage('-1461501637330902918203684832716283019655932542975n'); |
| tryPostMessage('4722366482869645213695n'); |
| tryPostMessage('-4722366482869645213695n'); |
| tryPostMessage('({})'); |
| tryPostMessage('({a:1})'); |
| tryPostMessage('({a:"a"})'); |
| tryPostMessage('({b:"a", a:"b"})'); |
| tryPostMessage('({p0:"string0", p1:"string1", p2:"string2", p3:"string3", p4:"string4", p5:"string5", p6:"string6", p7:"string7", p8:"string8", p9:"string9", p10:"string10", p11:"string11", p12:"string12", p13:"string13", p14:"string14", p15:"string15", p16:"string16", p17:"string17", p18:"string18", p19:"string19"})'); |
| tryPostMessage('({p0:"string1", p1:"string1", p2:"string2", p3:"string3", p4:"string4", p5:"string5", p6:"string6", p7:"string7", p8:"string8", p9:"string9", p10:"string10", p11:"string11", p12:"string12", p13:"string13", p14:"string14", p15:"string15", p16:"string16", p17:"string17", p18:"string18", p19:"string19"})'); |
| tryPostMessage('({a:""})'); |
| tryPostMessage('({a:0})'); |
| tryPostMessage('({a:1})'); |
| tryPostMessage('[]'); |
| tryPostMessage('["a", "a", "b", "a", "b"]'); |
| tryPostMessage('["a", "a", "b", {a:"b", b:"a"}]'); |
| tryPostMessage('[1,2,3]'); |
| tryPostMessage('[,,1]'); |
| tryPostMessage('(function(){})', true, null, DOMException.DATA_CLONE_ERR); |
| tryPostMessage('var x = 0; try { eval("badref"); } catch(e) { x = e; } x', true, null, DOMException.DATA_CLONE_ERR); |
| tryPostMessage('new Date(1234567890000)'); |
| tryPostMessage('new ConstructorWithPrototype("foo")', false, '({field:"foo"})'); |
| tryPostMessage('new Boolean(true)'); |
| tryPostMessage('new Boolean(false)'); |
| tryPostMessage('new String("gnirts")'); |
| tryPostMessage('new Number(42.0)'); |
| tryPostMessage('Object(0n)'); |
| tryPostMessage('Object(-20n)'); |
| tryPostMessage('Object(4294967295n)'); |
| tryPostMessage('Object(-4294967295n)'); |
| tryPostMessage('Object(2147483647n)'); |
| tryPostMessage('Object(2147483648n)'); |
| tryPostMessage('Object(-2147483648n)'); |
| tryPostMessage('Object(-2147483649n)'); |
| tryPostMessage('Object(68719476735n)'); |
| tryPostMessage('Object(-68719476735n)'); |
| tryPostMessage('Object(1000000000000000000000000000000000n)'); |
| tryPostMessage('Object(-1000000000000000000000000000000000n)'); |
| tryPostMessage('Object(1461501637330902918203684832716283019655932542975n)'); |
| tryPostMessage('Object(-1461501637330902918203684832716283019655932542975n)'); |
| tryPostMessage('Object(4722366482869645213695n)'); |
| tryPostMessage('Object(-4722366482869645213695n)'); |
| cyclicObject={}; |
| cyclicObject.self = cyclicObject; |
| tryPostMessage('cyclicObject', false, "cyclicObject"); |
| cyclicArray=[]; |
| cyclicArray[0] = cyclicArray; |
| tryPostMessage('cyclicArray', false, "cyclicArray"); |
| objectGraph = {}; |
| object = {}; |
| objectGraph.graph1 = object; |
| objectGraph.graph2 = object; |
| tryPostMessage('objectGraph', false, "objectGraph"); |
| arrayGraph = [object, object]; |
| tryPostMessage('arrayGraph', false, "arrayGraph"); |
| tryPostMessage('window', true); |
| tryPostMessage('({get a() { throw "x" }})', true); |
| |
| var map = new Map; |
| var set = new Set; |
| map.expando1 = {}; |
| map.expando2 = {}; |
| map.aSet = set; |
| map.set(1, 2.5) |
| map.set("entry", map.expando1); |
| map.set(true, set); |
| map.set(map.expando2, map); |
| set.add(false) |
| set.add(map) |
| tryPostMessage("map", false, "evalThunk", function (v) { |
| newMap = v |
| doPassFail(newMap.get("entry") === newMap.expando1, "String keyed entry was cloned correctly"); |
| doPassFail(newMap.get(newMap.expando2) === newMap, "Object key entry was cloned correctly"); |
| shouldBe("newMap.get(true)", "newMap.aSet") |
| shouldBe("newMap.aSet.has(newMap)", "true") |
| newMap.forEach(function (value, key) { |
| console.innerHTML += "LOG: " + value + " => " + key + "<br>" |
| }) |
| }) |
| |
| if (window.eventSender) { |
| var fileInput = document.getElementById("fileInput"); |
| var fileRect = fileInput.getClientRects()[0]; |
| var targetX = fileRect.left + fileRect.width / 2; |
| var targetY = fileRect.top + fileRect.height / 2; |
| eventSender.beginDragWithFiles(['resources/file1.txt', 'resources/file2.txt']); |
| eventSender.mouseMoveTo(targetX, targetY); |
| eventSender.mouseUp(); |
| } |
| var imageData = document.createElement("canvas").getContext("2d").createImageData(10,10); |
| for (var i = 0; i < imageData.data.length * 4; i++) |
| imageData.data[i] = i % 256; |
| var mutatedImageData = document.createElement("canvas").getContext("2d").createImageData(10,10); |
| for (var i = 0; i < imageData.data.length * 4; i++) |
| mutatedImageData.data[i] = i % 256; |
| tryPostMessage('imageData', false, imageData); |
| tryPostMessage('imageData.data', false, imageData.data) |
| tryPostMessage('mutatedImageData', false, imageData); |
| tryPostMessage('mutatedImageData.data', false, imageData.data) |
| for (var i = 0; i < imageData.data.length * 4; i++) |
| mutatedImageData.data[i] = 0; |
| |
| function thunk(s) { |
| return "(function() {" + s + "})()"; |
| } |
| tryPostMessage(thunk('return 42;'), false, '42'); |
| tryPostMessage(thunk('return 42;'), false, thunk('return 40 + 2;')); |
| tryPostMessage(thunk('return 42;'), false, "evalThunk", |
| function(v) { doPassFail(v == 42, "evalThunk OK"); }) |
| |
| // Only enumerable properties should be serialized. |
| tryPostMessage(thunk('var o = {x:"hello"}; Object.defineProperty(o, "y", {value:"goodbye"}); return o;'), false, '({x:"hello"})'); |
| |
| // It's unclear what we should do if an accessor modifies an object out from under us |
| // while we're serializing it; the standard does mandate certain aspects about evaluation |
| // order, though, including that properties must be processed in their enumeration order. |
| tryPostMessage(thunk( |
| 'var a = [0, 1, 2]; ' + |
| 'var b = { get x() { a[0] = 40; a[2] = 42; a.push(43); return 41; }}; ' + |
| 'a[1] = b; ' + |
| 'return a;' |
| ), false, "evalThunk", function(v) { |
| doPassFail(v.length === 3, "length correct"); // undefined |
| doPassFail(v[0] === 0, "evaluation order OK"); // mandatory |
| doPassFail(v[1].x === 41, "evaluation order OK/accessor reached"); // mandatory |
| doPassFail(v[2] === 42, "evaluation order OK"); // mandatory |
| }); |
| |
| tryPostMessage(thunk( |
| 'var a = [0, 1, 2]; ' + |
| 'var b = { get x() { a.pop(); return 41; } }; ' + |
| 'a[1] = b; ' + |
| 'return a;' |
| ), false, "evalThunk", function(v) { |
| doPassFail(v.length === 3 || v.length === 2, "length correct"); // undefined |
| doPassFail(v[0] === 0, "index 0 OK"); // mandatory |
| doPassFail(v[1].x === 41, "accessor reached"); // mandatory |
| doPassFail(v[2] === undefined, "index 2 undefined"); // undefined |
| }); |
| |
| tryPostMessage(thunk( |
| 'var a = [0, 1, 2]; ' + |
| 'var b = { get x() { a.pop(); return 41; } }; ' + |
| 'a[2] = b; ' + |
| 'return a;' |
| ), false, "evalThunk", function(v) { |
| doPassFail(v.length === 3, "length correct"); // undefined |
| doPassFail(v[0] === 0, "index 0 OK"); // mandatory |
| doPassFail(v[1] === 1, "index 1 OK"); // mandatory |
| doPassFail(v[2].x === 41, "index 2 OK"); // undefined |
| }); |
| |
| // Now with objects! This is a little more tricky because the standard does not |
| // define an enumeration order. |
| tryPostMessage(thunk( |
| 'var a = {p0: 0, p1: 1}; ' + |
| 'Object.defineProperty(a, "p2", {get:function() {delete a.p3; return 42; }, enumerable: true, configurable: true}); ' + |
| 'Object.defineProperty(a, "p3", {get:function() {delete a.p2; return 43; }, enumerable: true, configurable: true}); ' + |
| 'Object.defineProperty(a, "p4", {get:function() { a.p5 = 45; return 44; }, enumerable: true, configurable: true}); ' + |
| 'return a;' |
| ), false, "evalThunk", function(v) { |
| doPassFail(v.p0 === 0 && v.p1 === 1, "basic properties OK"); // mandatory |
| doPassFail(v.p2 === undefined && v.p3 !== undefined || |
| v.p2 !== undefined && v.p3 === undefined, "one accessor was run"); // mandatory |
| doPassFail(v.p2 !== undefined || Object.getOwnPropertyDescriptor(v, "p2") === undefined, "property was removed"); // undefined |
| doPassFail(v.p3 !== undefined || Object.getOwnPropertyDescriptor(v, "p3") === undefined, "property was removed"); // undefined |
| doPassFail(v.p4 === 44, "accessor was run"); // mandatory |
| doPassFail(Object.getOwnPropertyDescriptor(v, "p5") === undefined, "dynamic property not sent"); // undefined |
| }); |
| |
| // Objects returned from accessors should still be coalesced. |
| tryPostMessage(thunk( |
| 'var obja = {get p() { return 42; }}; ' + |
| 'var msg = {get a() { return obja; }, b: obja}; ' + |
| 'return msg;' |
| ), false, "evalThunk", function(v) { |
| // Interestingly, the standard admits two answers here! |
| doPassFail(v.a === v.b, "reference equality preserved"); |
| doPassFail((v.b.p === 42 && v.a.p === 42) || |
| (v.b.p === null && v.a.p === null), "accessors used"); |
| }); |
| tryPostMessage(thunk( |
| 'var obja = {get p() { return 42; }}; ' + |
| 'var msg = {a: obja, get b() { return obja; }}; ' + |
| 'return msg;' |
| ), false, "evalThunk", function(v) { |
| // Interestingly, the standard admits two answers here! |
| doPassFail(v.a === v.b, "reference equality preserved (opposite order)"); |
| doPassFail((v.b.p === 42 && v.a.p === 42) || |
| (v.b.p === null && v.a.p === null), "accessors used (opposite order)"); |
| }); |
| // We should nullify the results from accessors more than one level deep, |
| // but leave other fields untouched. |
| tryPostMessage(thunk( |
| 'var obja = {get p() { return 42; }, q: 43}; ' + |
| 'return {get a() { return obja; }};' |
| ), false, "evalThunk", function(v) { |
| doPassFail(v.a.p === null, "accessor value was nullified"); |
| doPassFail(v.a.q === 43, "non-accessor value was not nullified"); |
| }); |
| tryPostMessage(thunk( |
| 'var objb = {get r() { return 44; }, t: 45}; ' + |
| 'var obja = {get p() { return 42; }, q: 43, s: objb}; ' + |
| 'return {get a() { return obja; }};' |
| ), false, "evalThunk", function(v) { |
| doPassFail(v.a.p === null, "accessor value was nullified"); |
| doPassFail(v.a.q === 43, "non-accessor value was not nullified"); |
| doPassFail(v.s !== null, "non-accessor value was not nullified"); |
| doPassFail(v.a.s.r === null, "accessor value was nullified"); |
| doPassFail(v.a.s.t === 45, "non-accessor value was not nullified"); |
| }); |
| tryPostMessage(thunk( |
| 'var objb = {get r() { return 44; }, t: 45}; ' + |
| 'var obja = {get p() { return 42; }, q: 43, s: [objb]}; ' + |
| 'return {get c() { return 47; }, get a() { return obja; }, get b() { return 46; } };' |
| ), false, "evalThunk", function(v) { |
| doPassFail(v.b === 46, "accessor value was not nullified"); |
| doPassFail(v.c === 47, "accessor value was not nullified"); |
| doPassFail(v.a.p === null, "accessor value was nullified"); |
| doPassFail(v.a.q === 43, "non-accessor value was not nullified"); |
| doPassFail(v.a.s !== null, "non-accessor value was not nullified"); |
| doPassFail(v.a.s !== undefined, "non-accessor value is defined"); |
| doPassFail(v.a.s[0] !== null, "non-accessor value was not nullified"); |
| doPassFail(v.a.s[0] !== undefined, "non-accessor value is defined"); |
| doPassFail(v.a.s[0].r === null, "accessor value was nullified"); |
| doPassFail(v.a.s[0].t === 45, "non-accessor value was not nullified"); |
| }); |
| |
| // We need to pass out the exception raised from internal accessors. |
| tryPostMessage(thunk( |
| 'return {get a() { throw "accessor-exn"; }};' |
| ), true, null, 'accessor-exn'); |
| // We should still return the exception raised from nulled-out accessors. |
| tryPostMessage(thunk( |
| 'var obja = {get p() { throw "accessor-exn"; }}; ' + |
| 'return {get a() { return obja; }};' |
| ), true, null, 'accessor-exn'); |
| // We should run the first nullified accessor, but no more. |
| tryPostMessage(thunk( |
| 'window.bcalled = undefined; ' + |
| 'window.acalled = undefined; ' + |
| 'window.pcalled = undefined; ' + |
| 'var objb = {get b() { window.bcalled = true; return 42; }}; ' + |
| 'var obja = {get a() { window.acalled = true; return objb; }}; ' + |
| 'return { get p() { window.pcalled = true; return obja; }};' |
| ), false, "evalThunk", function(v) { |
| doPassFail(v.p.a === null, "accessor value was nullified"); |
| doPassFail(window.pcalled === true, "window.pcalled === true"); |
| doPassFail(window.acalled === true, "window.acalled === true"); |
| doPassFail(window.bcalled === undefined, "window.bcalled === undefined"); |
| }); |
| |
| // Reference equality between Boolean objects must be maintained. |
| tryPostMessage(thunk( |
| 'var t1 = new Boolean(true); ' + |
| 'var t2 = new Boolean(true); ' + |
| 'var f1 = new Boolean(false); ' + |
| 'var f2 = new Boolean(false); ' + |
| 'return [t1, t1, t2, f1, f1, f2];' |
| ), false, "evalThunk", function(v) { |
| doPassFail(equal(v[0], new Boolean(true)), "Boolean values correct (0)"); |
| doPassFail(equal(v[3], new Boolean(false)), "Boolean values correct (3)"); |
| doPassFail(equal(v[1], v[2]), "Boolean values correct (1,2)"); |
| doPassFail(equal(v[4], v[5]), "Boolean values correct (4,5)"); |
| doPassFail(v[0] === v[1], "References to Booleans correct (0,1)"); |
| doPassFail(v[3] === v[4], "References to Booleans correct (3,4)"); |
| doPassFail(v[0] !== v[2], "References to Booleans correct (0,2)"); |
| doPassFail(v[3] !== v[5], "References to Booleans correct (3,5)"); |
| }); |
| |
| // Reference equality between Number objects must be maintained. |
| tryPostMessage(thunk( |
| 'var n1 = new Number(42.0); ' + |
| 'var n2 = new Number(42.0); ' + |
| 'return [n1, n1, n2];' |
| ), false, "evalThunk", function(v) { |
| doPassFail(equal(v[0], new Number(42.0)), "Number values correct (0)"); |
| doPassFail(equal(v[0], v[2]), "Number values correct (0,2)"); |
| doPassFail(v[0] === v[1], "References to numbers correct (0,1)"); |
| doPassFail(v[0] !== v[2], "References to numbers correct (0,2)"); |
| }); |
| |
| // Reference equality between String objects must be maintained. |
| tryPostMessage(thunk( |
| 'var s1 = new String("gnirts"); ' + |
| 'var s2 = new String("gnirts"); ' + |
| 'return [s1, s1, s2];' |
| ), false, "evalThunk", function(v) { |
| doPassFail(equal(v[0], new String("gnirts")), "String values correct (0)"); |
| doPassFail(equal(v[0], v[2]), "String values correct (0,2)"); |
| doPassFail(v[0] === v[1], "References to strings correct (0,1)"); |
| doPassFail(v[0] !== v[2], "References to strings correct (0,2)"); |
| }); |
| |
| // Properties added to String, Boolean, Number, and BigInt objects should not be serialized. |
| tryPostMessage(thunk( |
| 'var s = new String("gnirts"); ' + |
| 'var n = new Number(42.0); ' + |
| 'var b = new Boolean(true); ' + |
| 'var i = Object(100000000000000000000n); ' + |
| 'var i32 = Object(10n); ' + |
| 's.foo = 1; n.foo = 2; b.foo = 3; i.foo = 4; i32.foo = 5; ' + |
| 'return [s, n, b, i, i32];' |
| ), false, "evalThunk", function(v) { |
| doPassFail(v[0].foo == undefined, "String object properties not serialized"); |
| doPassFail(v[1].foo == undefined, "Number object properties not serialized"); |
| doPassFail(v[2].foo == undefined, "Boolean object properties not serialized"); |
| doPassFail(v[3].foo == undefined, "BigInt object properties not serialized"); |
| doPassFail(v[4].foo == undefined, "BigInt (BigInt32) object properties not serialized"); |
| }); |
| |
| // Reference equality between dates must be maintained. |
| tryPostMessage(thunk( |
| 'var d1 = new Date(1,2,3); ' + |
| 'var d2 = new Date(1,2,3); ' + |
| 'return [d1,d1,d2];' |
| ), false, "evalThunk", function(v) { |
| doPassFail(equal(v[0], new Date(1,2,3)), "Date values correct (0)"); |
| doPassFail(equal(v[0], v[2]), "Date values correct (1)"); |
| doPassFail(v[0] === v[1], "References to dates correct (0)"); |
| doPassFail(v[0] !== v[2], "References to dates correct (1)"); |
| }); |
| |
| // Reference equality between regexps must be preserved. |
| tryPostMessage(thunk( |
| 'var rx1 = new RegExp("foo"); ' + |
| 'var rx2 = new RegExp("foo"); ' + |
| 'var rx3 = new RegExp("foo", "gim"); ' + |
| 'rx3.exec("foofoofoo"); ' + |
| 'doPassFail(rx3.lastIndex === 3, "lastIndex initially correct: was " + rx3.lastIndex); ' + |
| 'return [rx1,rx1,rx2,rx3];' |
| ), false, "evalThunk", function(v) { |
| doPassFail(v[0].source === "foo", "Regexp value correct (0)"); |
| doPassFail(v[0] === v[1], "References to regexps correct (0)"); |
| doPassFail(v[0] !== v[2], "References to regexps correct (1)"); |
| doPassFail(v[0].global === false, "global set (0)"); |
| doPassFail(v[3].global === true, "global set (1)"); |
| doPassFail(v[0].ignoreCase === false, "ignoreCase set (0)"); |
| doPassFail(v[3].ignoreCase === true, "ignoreCase set (1)"); |
| doPassFail(v[0].multiline === false, "multiline set (0)"); |
| doPassFail(v[3].multiline === true, "multiline set (1)"); |
| doPassFail(v[0].lastIndex === 0, "lastIndex correct (0)"); |
| doPassFail(v[3].lastIndex === 0, "lastIndex correct (1)"); |
| }); |
| |
| if (window.eventSender) { |
| tryPostMessage(thunk( |
| 'window.fileList = fileInput.files; ' + |
| 'window.file0 = fileList[0]; ' + |
| 'window.file1 = fileList[1]; ' + |
| 'return window.fileList.length'), false, 2); |
| doPassFail(window.fileList[0] === window.file0, "sanity on file reference equality") |
| // The standard mandates that we do _not_ maintain reference equality for files in a transferred FileList. |
| tryPostMessage(thunk('return [window.file0, window.file0];' |
| ), false, "evalThunk", function(v) { doPassFail(v[0] === v[1], "file references transfer")}); |
| tryPostMessage(thunk('return [window.fileList, window.file0];' |
| ), false, "evalThunk", function(v) { doPassFail(v[0][0] !== v[1], "FileList should not respect reference equality")}); |
| tryPostMessage(thunk('return [window.file0, window.fileList];' |
| ), false, "evalThunk", function(v) { doPassFail(v[1][0] !== v[0], "FileList should not respect reference equality")}); |
| tryPostMessage(thunk('return [window.fileList, window.fileList];' |
| ), false, "evalThunk", function(v) { doPassFail(v[0] === v[1], "FileList respects self-reference equality")}); |
| tryPostMessage(thunk('return [window.fileList, window.file0, window.file1]' |
| ), false, "evalThunk", function(v) { |
| doPassFail(v[0].length === window.fileList.length, "FileList length sent correctly"); |
| doPassFail(v[0][0] !== v[1], "FileList should not respect reference equality (0)"); |
| doPassFail(v[0][1] !== v[2], "FileList should not respect reference equality (1)"); |
| doPassFail(v[0][0].name == window.file0.name, "FileList preserves order and data (name0)"); |
| doPassFail(v[0][1].name == window.file1.name, "FileList preserves order and data (name1)"); |
| doPassFail(equal(v[0][0].lastModifiedDate, window.file0.lastModifiedDate), "FileList preserves order and data (date0)"); |
| doPassFail(equal(v[0][1].lastModifiedDate, window.file1.lastModifiedDate), "FileList preserves order and data (date1)"); |
| }); |
| } |
| tryPostMessage('"done"'); |
| </script> |
| </body> |
| </html> |