| <!doctype html> |
| <html> |
| <head> |
| <meta content="text/html; charset=utf-8" http-equiv="content-type" /> |
| <title>2.8 Common DOM interfaces - Structured Clone Algorithm </title> |
| <link rel="help" href="http://www.w3.org/TR/html5/common-dom-interfaces.html#safe-passing-of-structured-data" /> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| </head> |
| <body> |
| <div id="log"></div> |
| |
| <script type="text/javascript"> |
| var worker; |
| var testCollection; |
| setup(function() |
| { |
| //the worker is used for each test in sequence |
| //worker's callback will be set for each test |
| //worker's internal onmessage echoes the data back to this thread through postMessage |
| worker = new Worker("./echo.js"); |
| testCollection = [ |
| function() { |
| var t = async_test("Primitive string is cloned"); |
| t.id = 0; |
| worker.onmessage = t.step_func(function(e) {assert_equals("primitive string", e.data, "\"primitive string\" === event.data"); t.done(); }); |
| t.step(function() { worker.postMessage("primitive string");}); |
| }, |
| function() { |
| var t = async_test("Primitive integer is cloned"); |
| t.id = 1; |
| worker.onmessage = t.step_func(function(e) {assert_equals(2000, e.data, "2000 === event.data"); t.done(); }); |
| t.step(function() { worker.postMessage(2000);}); |
| }, |
| function() { |
| var t = async_test("Primitive floating point is cloned"); |
| t.id = 2; |
| worker.onmessage = t.step_func(function(e) {assert_equals(111.456, e.data, "111.456 === event.data"); t.done(); }); |
| t.step(function() { worker.postMessage(111.456);}); |
| }, |
| function() { |
| var t = async_test("Primitive floating point (negative) is cloned"); |
| t.id = 3; |
| worker.onmessage = t.step_func(function(e) {assert_equals(-111.456, e.data, "-111.456 === event.data"); t.done(); }); |
| t.step(function() { worker.postMessage(-111.456);}); |
| }, |
| function() { |
| var t = async_test("Primitive number (hex) is cloned"); |
| t.id = 4; |
| worker.onmessage = t.step_func(function(e) {assert_equals(0xAB25, e.data, "0xAB25 === event.data"); t.done(); }); |
| t.step(function() { worker.postMessage(0xAB25);}); |
| }, |
| function() { |
| var t = async_test("Primitive number (scientific) is cloned"); |
| t.id = 5; |
| worker.onmessage = t.step_func(function(e) {assert_equals(15e2, e.data, "15e2 === event.data"); t.done(); }); |
| t.step(function() { worker.postMessage(15e2);}); |
| }, |
| function() { |
| var t = async_test("Primitive boolean is cloned"); |
| t.id = 6; |
| worker.onmessage = t.step_func(function(e) {assert_equals(false, e.data, "false === event.data"); t.done(); }); |
| t.step(function() { worker.postMessage(false);}); |
| }, |
| function() { |
| var t = async_test("Instance of Boolean is cloned"); |
| t.id = 7; |
| var obj; |
| t.step(function() {obj = new Boolean(false);}); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "Boolean === event.data.constructor"); |
| assert_equals(obj.valueOf(), e.data.valueOf(), "(new Boolean(false)).valueof() === event.data.valueOf()"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| },function() { |
| var t = async_test("Instance of Number is cloned"); |
| t.id = 8; |
| var obj; |
| t.step(function() {obj = new Number(2000);}); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "Number === event.data.constructor"); |
| assert_equals(obj.valueOf(), e.data.valueOf(), "(new Number(2000)).valueof() === event.data.valueOf()"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("Instance of String is cloned"); |
| t.id = 9; |
| var obj; |
| t.step(function() { obj = new String("String Object");}); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "String === event.data.constructor"); |
| assert_equals(obj.valueOf(), e.data.valueOf(), "(new String(\"String Object\")).valueof() === event.data.valueOf()"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("Instance of Date is cloned"); |
| t.id = 10; |
| var obj; |
| t.step(function() { obj= new Date(2011,1,1);}); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "Date === event.data.constructor"); |
| assert_equals(obj.valueOf(), e.data.valueOf(), "(new Date(2011,1,1)).valueof() === event.data.valueOf()"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("Instance of RegExp is cloned"); |
| t.id = 11; |
| var obj; |
| t.step(function() {obj = new RegExp("w3+c","g","i");}); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "RegExp === event.data.constructor"); |
| assert_equals(obj.source, e.data.source, "canon.source === event.data.source"); |
| assert_equals(obj.multiline, e.data.multiline, "canon.multiline === event.data.multiline"); |
| assert_equals(obj.global, e.data.global, "canon.global === event.data.global"); |
| assert_equals(obj.ignoreCase, e.data.ignoreCase, "canon.ignoreCase === event.data.ignoreCase"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("Value 'null' is cloned"); |
| t.id = 12; |
| worker.onmessage = t.step_func(function(e) {assert_equals(null, e.data, "null === event.data"); t.done(); }); |
| t.step(function() { worker.postMessage(null);}); |
| }, |
| function() { |
| var t = async_test("Value 'undefined' is cloned"); |
| t.id = 13; |
| worker.onmessage = t.step_func(function(e) {assert_equals(undefined, e.data, "undefined === event.data"); t.done(); }); |
| t.step(function() { worker.postMessage(undefined);}); |
| }, |
| function() { |
| var t = async_test("Object properties are cloned"); |
| t.id = 14; |
| var obj; |
| t.step(function() { |
| obj= {}; |
| obj.a = "test"; |
| obj.b = 2; |
| obj["child"] = 3; |
| }); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor"); |
| assert_equals(obj.a, e.data.a, "canon.a === event.data.a"); |
| assert_equals(obj.b, e.data.b, "canon.b === event.data.b"); |
| assert_equals(obj.child, e.data.child, "canon.child === e.data.child"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("Prototype chains are not walked."); |
| t.id = 15; |
| function Custom() { |
| this.a = "hello"; |
| } |
| |
| var obj; |
| t.step(function() { |
| Object.defineProperty(Custom.prototype, "b", { enumerable: true, value: 100 }); |
| obj = new Custom(); |
| }); |
| worker.onmessage = t.step_func(function(e) { |
| assert_not_equals(obj.constructor, e.data.constructor, "canon.constructor !== event.data.constructor"); |
| assert_equals(Object, e.data.constructor, "Object === e.data.constructor"); |
| assert_equals(obj.a, e.data.a, "canon.a === e.data.a"); |
| assert_equals(undefined, e.data.b, "undefined === e.data.b"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("Property descriptors of Objects are not cloned"); |
| t.id = 16; |
| var obj; |
| t.step(function() { |
| obj = {}; |
| Object.defineProperty(obj, "a", { enumerable: true, writable: false, value: 100 }); |
| }); |
| worker.onmessage = t.step_func(function(e) { |
| var des = Object.getOwnPropertyDescriptor(e.data, "a"); |
| assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor"); |
| assert_true(des.writable, "Descriptor is writable"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("Cycles are preserved in Objects"); |
| t.id = 17; |
| var obj; |
| t.step(function() { |
| obj = {}; |
| obj.a = obj; |
| }); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor"); |
| assert_equals(e.data, e.data.a, "cycle is preserved"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("Identity of duplicates is preserved"); |
| t.id = 18; |
| var ref; |
| var obj; |
| t.step(function() { |
| ref = {}; |
| ref.called = 0; |
| Object.defineProperty(ref, "child", {get: function(){this.called++;}, enumerable: true}); |
| |
| obj = {a:ref, b:ref}; |
| }); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor"); |
| assert_equals(e.data.b.called, 0, "e.data.b.called === 0"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("Property order is preserved"); |
| t.id = 19; |
| var obj; |
| t.step(function() { |
| obj = { "a": "hello", "b": "w3c", "c": "and world" }; |
| obj["a"] = "named1"; |
| }); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor"); |
| var canonNames = Object.getOwnPropertyNames(obj); |
| var testNames = Object.getOwnPropertyNames(e.data); |
| for (var i in canonNames) { |
| assert_equals(canonNames[i], testNames[i], "canonProperty["+i+"] === dataProperty["+i+"]"); |
| } |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("Enumerable properties of Arrays are cloned"); |
| t.id = 20; |
| var obj; |
| t.step(function() { |
| obj = [0,1]; |
| obj["a"] = "named1"; |
| }); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor"); |
| assert_equals(e.data["a"], "named1", "e.data[\"a\"] === \"named1\""); |
| assert_equals(e.data[0], 0, "e.data[0] === 0"); |
| assert_equals(e.data[1], 1, "e.data[1] === 1"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("Property descriptors of Arrays are not cloned"); |
| t.id = 21; |
| var obj; |
| t.step(function() { |
| obj = [0, 1]; |
| Object.defineProperty(obj, "2", { enumerable: true, writable: false, value: 100 }); |
| }); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor"); |
| assert_equals(e.data[0], 0, "e.data[0] === 0"); |
| assert_equals(e.data[1], 1, "e.data[1] === 1"); |
| var des = Object.getOwnPropertyDescriptor(e.data, "2"); |
| assert_true(des.writable, "Descriptor is writable"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("Cycles are preserved in Arrays"); |
| t.id = 22; |
| var obj; |
| t.step(function() { |
| obj = [0,1]; |
| obj[2] = obj; |
| }); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor"); |
| assert_equals(e.data[0], 0, "e.data[0] === 0"); |
| assert_equals(e.data[1], 1, "e.data[1] === 1"); |
| assert_equals(e.data[2], e.data, "e.data[2] === e.data"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| |
| function() { |
| var t = async_test("ImageData object can be cloned"); |
| t.id = 23; |
| var obj; |
| t.step(function() { |
| var canvas = document.createElement("canvas"); |
| canvas.width = 40; |
| canvas.height = 40; |
| var context = canvas.getContext('2d'); |
| obj = context.createImageData(40, 40); |
| assert_true(window.hasOwnProperty("ImageData"), "ImageData constructor must be present"); |
| assert_true(obj instanceof ImageData, "ImageData must be returned by .createImageData"); |
| }); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor"); |
| assert_not_equals(obj, e.data, "cloned object should be a new instance of ImageData"); |
| assert_equals(obj.width, e.data.width, "canon.width === e.data.width"); |
| assert_equals(obj.height, e.data.height, "canon.height === e.data.height"); |
| assert_array_equals(obj.data, e.data.data, "data arrays are the same"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("ImageData expandos are not cloned"); |
| t.id = 24; |
| var obj; |
| t.step(function() { |
| var canvas = document.createElement("canvas"); |
| canvas.width = 40; |
| canvas.height = 40; |
| var context = canvas.getContext('2d'); |
| obj = context.createImageData(40, 40); |
| assert_true(window.hasOwnProperty("ImageData"), "ImageData constructor must be present"); |
| assert_true(obj instanceof ImageData, "ImageData must be returned by .createImageData"); |
| obj.foo = "bar"; |
| }); |
| worker.onmessage = t.step_func(function(e) { |
| assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor"); |
| assert_not_equals(obj, e.data, "cloned object should be a new instance of ImageData"); |
| assert_equals(obj.width, e.data.width, "canon.width === e.data.width"); |
| assert_equals(obj.height, e.data.height, "canon.height === e.data.height"); |
| assert_array_equals(obj.data, e.data.data, "data arrays are the same"); |
| assert_equals(undefined, e.data.foo, "Expando is lost (undefined === e.data.foo)"); |
| t.done(); |
| }); |
| t.step(function() { worker.postMessage(obj);}); |
| }, |
| function() { |
| var t = async_test("Window objects cannot be cloned"); |
| t.id = 25; |
| worker.onmessage = function() {}; //no op because exception should be thrown. |
| t.step(function() { |
| assert_true(DOMException.hasOwnProperty('DATA_CLONE_ERR'), "DOMException.DATA_CLONE_ERR is present"); |
| assert_equals(DOMException.DATA_CLONE_ERR, 25, "DOMException.DATA_CLONE_ERR === 25"); |
| assert_throws('DATA_CLONE_ERR', function() {worker.postMessage(window)}); |
| }); |
| t.done(); |
| }, |
| function() { |
| var t = async_test("Document objects cannot be cloned"); |
| t.id = 26; |
| worker.onmessage = function() {}; //no op because exception should be thrown. |
| t.step(function() { |
| assert_true(DOMException.hasOwnProperty('DATA_CLONE_ERR'), "DOMException.DATA_CLONE_ERR is present"); |
| assert_equals(DOMException.DATA_CLONE_ERR, 25, "DOMException.DATA_CLONE_ERR === 25"); |
| assert_throws('DATA_CLONE_ERR', function() {worker.postMessage(document)}); |
| }); |
| t.done(); |
| } |
| ]; |
| }, {explicit_done:true}); |
| |
| //Callback for result_callback |
| //queues the next test in the array testCollection |
| //serves to make test execution sequential from the async worker callbacks |
| //alternatively, we would have to create a worker for each test |
| function testFinished(test) { |
| if(test.id < testCollection.length - 1) { |
| //queue the function so that stack remains shallow |
| queue(testCollection[test.id+1]); |
| } else { |
| //when the last test has run, explicitly end test suite |
| done(); |
| } |
| } |
| function queue(func) { |
| setTimeout(func, 10); |
| } |
| |
| add_result_callback(testFinished); |
| //start the first test manually |
| queue(testCollection[0]); |
| |
| |
| |
| |
| </script> |
| </body> |
| </html> |