<?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin" type="text/css"?> <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> <window title="MessageManager CPOW tests" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="start()"> <!-- test results are displayed in the html:body --> <label value="CPOWs"/> <script type="application/javascript"><![CDATA[ var test_state = "remote"; var test_node = null; var reentered = false; var savedMM = null; const Cu = Components.utils; function info(message) { return opener.wrappedJSObject.info(message); } function ok(condition, message) { return opener.wrappedJSObject.ok(condition, message); } function is(v1, v2, message) { return opener.wrappedJSObject.is(v1, v2, message); } function todo_is(v1, v2, message) { return opener.wrappedJSObject.todo_is(v1, v2, message); } // Make sure that an error in this file actually causes the test to fail. var gReceivedErrorProbe = false; window.onerror = function (msg, url, line) { if (/Test Error Probe/.test(msg)) { gReceivedErrorProbe = true; return; } ok(false, "Error while executing: \n" + msg + "\n" + url + ":" + line); }; function testCpowMessage(message) { ok(message.json.check == "ok", "correct json"); ok(!Components.utils.isCrossProcessWrapper(message.json), "not everything is a CPOW"); let data = message.objects.data; let document = message.objects.document; if (test_state == "remote") { ok(Components.utils.isCrossProcessWrapper(data), "got a CPOW"); ok(Components.utils.isCrossProcessWrapper(document), "got a CPOW"); } ok(data.i === 5, "integer property"); ok(data.b === true, "boolean property"); ok(data.s === "hello", "string property"); ok(data.x.i === 10, "nested property"); ok(data.f() === 99, "function call"); is(Object.getOwnPropertyDescriptor(data, "doesn't exist"), undefined, "getOwnPropertyDescriptor returns undefined for non-existant properties"); ok(Object.getOwnPropertyDescriptor(data, "i").value, 5, "getOwnPropertyDescriptor.value works"); let obj = new data.ctor(); ok(obj.a === 3, "constructor call"); is(document.title, "Hello, Kitty", "document node"); is(typeof document.cookie, "string", "can get document.cookie"); is(typeof document.defaultView.navigator.userAgent, "string", "can get navigator.userAgent"); // Don't crash. document.defaultView.screen; data.i = 6; data.b = false; data.s = "bye"; data.x = null; ok(data.i === 6, "integer property"); ok(data.b === false, "boolean property"); ok(data.s === "bye", "string property"); ok(data.x === null, "nested property"); let throwing = message.objects.throwing; // Based on the table on: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy let tests = [ () => Object.getOwnPropertyDescriptor(throwing, 'test'), () => Object.getOwnPropertyNames(throwing), () => Object.defineProperty(throwing, 'test', {value: 1}), () => delete throwing.test, () => "test" in throwing, () => Object.prototype.hasOwnProperty.call(throwing, 'test'), () => throwing.test, () => { throwing.test = 1 }, // () => { for (let prop in throwing) {} }, Bug 783829 () => { for (let prop of throwing) {} }, () => Object.keys(throwing), () => Function.prototype.call.call(throwing), () => new throwing, () => Object.preventExtensions(throwing), () => Object.freeze(throwing), () => Object.seal(throwing), ] for (let test of tests) { let threw = false; try { test() } catch (e) { threw = true; } ok(threw, "proxy operation threw exception"); } let array = message.objects.array; let i = 1; for (let elt of array) { ok(elt === i, "correct element found"); i++; } ok(i === 4, "array has correct length"); let j = message.objects.for_json; let str = JSON.stringify(j); let j2 = JSON.parse(str); ok(j2.n === 3, "JSON integer property"); ok(j2.a[0] === 1, "JSON array index"); ok(j2.a[1] === 2, "JSON array index"); ok(j2.a[2] === 3, "JSON array index"); ok(j2.s === "hello", "JSON string property"); ok(j2.o.x === 10, "JSON object property"); let with_proto = message.objects.with_proto; let proto = Object.getPrototypeOf(with_proto); ok(proto.data == 42, "Object.getPrototypeOf works on CPOW"); let with_null_proto = message.objects.with_null_proto; proto = Object.getPrototypeOf(with_null_proto); ok(proto === null, "Object.getPrototypeOf works on CPOW (null proto)"); } function recvAsyncMessage(message) { testCpowMessage(message); savedMM.sendAsyncMessage("cpows:async_done"); } function recvSyncMessage(message) { testCpowMessage(message); } function recvRpcMessage(message) { ok(message.json.check == "ok", "correct json"); let data = message.objects.data; // Sanity check. ok(data.i === 5, "integer property"); // Check that we re-enter. reentered = false; let result = data.reenter(); ok(reentered, "re-entered rpc"); ok(result == "ok", "got correct result"); } function recvReenterMessage(message) { ok(message.objects.data.valid === true, "cpows work"); reentered = true; } function recvNestedSyncMessage(message) { message.objects.data.reenter(); } function recvReenterSyncMessage(message) { ok(false, "should not have received re-entered sync message"); } function recvFailMessage(message) { ok(false, message.json.message); } function recvDoneMessage(message) { if (test_state == "remote") { test_node.parentNode.removeChild(test_node); run_tests("inprocess"); return; } finish(); } function recvParentTest(message) { let func = message.objects.func; let result = func(n => 2*n); ok(result == 20, "result == 20"); function f() { return 101; } let obj = {a:1, __exposedProps__: {"a": "r"}}; savedMM.sendAsyncMessage("cpows:from_parent", {}, {obj: obj, func: f}); } // Make sure errors in this file actually hit window.onerror. function recvErrorReportingTest(message) { throw "Test Error Probe"; } let savedElement = null; function recvDomTest(message) { savedElement = message.objects.element; is(savedElement.QueryInterface(Components.interfaces.nsISupports), savedElement, "QI to nsISupports works"); is(savedElement.QueryInterface(Components.interfaces.nsIDOMNode), savedElement, "QI to a random (implemented) interface works"); function testNoInterface(savedElement, i) { try { savedElement.QueryInterface(i); ok(false, "should have thrown an exception"); } catch (e) { is(e.result, Components.results.NS_ERROR_NO_INTERFACE, "threw the right exception"); } } testNoInterface(savedElement, Components.interfaces.nsIDOMAttr); testNoInterface(savedElement, Components.interfaces.nsIClassInfo); // Test to ensure that we don't pass CPOWs to C++-implemented interfaces. // See bug 1072980. if (test_state == "remote") { // This doesn't work because we intercept toString and QueryInterface specially // and don't cache the function pointer. // See bug 1140636. todo_is(savedElement.toString, savedElement.toString, "toString identity works"); todo_is(savedElement.QueryInterface, savedElement.QueryInterface, "toString identity works"); is(Object.prototype.toString.call(savedElement), "[object HTMLDivElement]", "prove that this works (and doesn't leak)"); is(Object.prototype.toString.call(savedElement), "[object HTMLDivElement]", "prove that this works twice (since we cache it and doesn't leak)"); // This does work because we create a CPOW for isEqualNode that stays // alive as long as we have a reference to the first CPOW (so as long // as it's detectable). is(savedElement.isEqualNode, savedElement.isEqualNode, "webidl function identity works"); let walker = Components.classes["@mozilla.org/inspector/deep-tree-walker;1"] .createInstance(Components.interfaces.inIDeepTreeWalker); const SHOW_ELEMENT = Components.interfaces.nsIDOMNodeFilter.SHOW_ELEMENT; walker.showAnonymousContent = true; walker.showSubDocuments = false; try { walker.init(savedElement, SHOW_ELEMENT); ok(false, "expected exception passing CPOW to C++"); } catch (e) { is(e.result, Components.results.NS_ERROR_XPC_CANT_PASS_CPOW_TO_NATIVE, "got exception when passing CPOW to C++"); } } } function recvDomTestAfterGC(message) { let id; try { id = savedElement.id; } catch (e) { ok(false, "Got exception using DOM element"); } is(id, "it_works", "DOM element has expected ID"); } function recvXrayTest(message) { let element = message.objects.element; is(element.foo, undefined, "DOM element does not expose content properties"); } function recvSymbolTest(message) { let object = message.objects.object; is(object[Symbol.iterator], Symbol.iterator, "Should use Symbol.iterator"); is(Symbol.keyFor(object[Symbol.for("cpow-test")]), "cpow-test", "Symbols aren't registered correctly"); let symbols = Object.getOwnPropertySymbols(object); is(symbols.length, 2, "Object should have two symbol keys"); let test = undefined; for (let x of message.objects.test) { test = x; } is(test, "a", "for .. of iteration should work"); } let systemGlobal = this; function recvCompartmentTest(message) { let getUnprivilegedObject = message.objects.getUnprivilegedObject; let testParentObject = message.objects.testParentObject; // Make sure that parent->child CPOWs live in the parent's privileged junk scope. let unprivilegedObject = getUnprivilegedObject(); is(Cu.getGlobalForObject(getUnprivilegedObject), Cu.getGlobalForObject(unprivilegedObject), "all parent->child CPOWs should live in the same scope"); let cpowLocation = Cu.getCompartmentLocation(getUnprivilegedObject); ok(/Privileged Junk/.test(cpowLocation), "parent->child CPOWs should live in the privileged junk scope: " + cpowLocation); // Make sure that parent->child CPOWs point through a privileged scope in the child // (the privileged junk scope, but we don't have a good way to test for that // specifically). is(unprivilegedObject.expando, undefined, "parent->child references should get Xrays"); is(unprivilegedObject.wrappedJSObject.expando, 42, "parent->child references should get waivable Xrays"); // Send an object to the child to let it verify invariants in the other direction. function passMe() { return 42; }; passMe.expando = 42; let results = testParentObject(passMe); ok(results.length > 0, "Need results"); results.forEach((x) => is(x.result, "PASS", x.message)); } function recvRegExpTest(message) { let regexp = message.objects.regexp; // These work generically. is(regexp.toString(), "/myRegExp/g", "toString works right"); ok(regexp.test("I like myRegExp to match"), "No false positives"); ok(!regexp.test("asdfsdf"), "No false positives"); // These go over regexp_toShared. is("filler myRegExp filler".search(regexp), 7, "String.prototype.match works right"); var shell = /x/; shell.compile(regexp); is(regexp.toString(), shell.toString(), ".compile works right"); } function recvPostMessageTest(message) { let win = message.objects.win; win.postMessage('nookery', '*'); ok(true, "Didn't crash invoking postMessage over CPOW"); } let savedWilldieObj; let wontDie = {f:2, __exposedProps__: {"f": "r"}}; function recvLifetimeTest1(message) { let obj = message.objects.obj; savedWilldieObj = obj.will_die; ok(savedWilldieObj.f == 1, "limited-lifetime CPOW works at first"); obj.wont_die = wontDie; obj = null; return 10; } function recvLifetimeTest2(message) { let threw = false; try { savedWilldieObj.f; } catch (e) { threw = true; } ok(threw, "limited-lifetime CPOW stopped working"); wontDie = null; Components.utils.schedulePreciseGC(function() { savedMM.sendAsyncMessage("cpows:lifetime_test_3"); }); } function recvCancelTest(msg) { let failed = false; try { msg.objects.f(); } catch (e if /cross-process JS call failed/.test(String(e))) { failed = true; } ok(failed, "CPOW should fail due to cancelation"); msg.target.messageManager.sendAsyncMessage("cpows:cancel_test_done"); } function recvCancelSyncMessage() { return 12; } function recvCancelTest2(msg) { let failed = false; try { msg.objects.f(); } catch (e if /cross-process JS call failed/.test(String(e))) { failed = true; } ok(failed, "CPOW should fail due to cancelation"); msg.target.messageManager.sendAsyncMessage("cpows:cancel_test2_done"); } function recvUnsafe(msg) { let failed = false; const PREF_UNSAFE_FORBIDDEN = "dom.ipc.cpows.forbid-unsafe-from-browser"; opener.wrappedJSObject.SpecialPowers.setBoolPref(PREF_UNSAFE_FORBIDDEN, true); try { msg.objects.f(); } catch (e if /unsafe CPOW usage forbidden/.test(String(e))) { failed = true; } opener.wrappedJSObject.SpecialPowers.clearUserPref(PREF_UNSAFE_FORBIDDEN); ok(failed, "CPOW should fail when unsafe"); msg.target.messageManager.sendAsyncMessage("cpows:unsafe_done"); } function recvSafe(msg) { const PREF_UNSAFE_FORBIDDEN = "dom.ipc.cpows.forbid-unsafe-from-browser"; opener.wrappedJSObject.SpecialPowers.setBoolPref(PREF_UNSAFE_FORBIDDEN, true); try { msg.objects.f(); } catch (e if /unsafe CPOW usage forbidden/.test(String(e))) { ok(false, "cpow failed"); } opener.wrappedJSObject.SpecialPowers.clearUserPref(PREF_UNSAFE_FORBIDDEN); msg.target.messageManager.sendAsyncMessage("cpows:safe_done"); } function recvDead(msg) { // Need to do this in a separate turn of the event loop. setTimeout(() => { msg.objects.gcTrigger(); try { msg.objects.thing.value; ok(false, "Should have been a dead CPOW"); } catch(e if /dead CPOW/.test(String(e))) { ok(true, "Got the expected dead CPOW"); ok(e.stack, "The exception has a stack"); } msg.target.messageManager.sendAsyncMessage("cpows:dead_done"); }, 0); } function run_tests(type) { info("Running tests: " + type); var node = document.getElementById('cpowbrowser_' + type); test_state = type; test_node = node; function recvIsRemote(message) { return type == "remote"; } var mm = node.messageManager; savedMM = mm; mm.addMessageListener("cpows:is_remote", recvIsRemote); mm.addMessageListener("cpows:async", recvAsyncMessage); mm.addMessageListener("cpows:sync", recvSyncMessage); mm.addMessageListener("cpows:rpc", recvRpcMessage); mm.addMessageListener("cpows:reenter", recvReenterMessage); mm.addMessageListener("cpows:reenter", recvReenterMessage); mm.addMessageListener("cpows:nested_sync", recvNestedSyncMessage); mm.addMessageListener("cpows:reenter_sync", recvReenterSyncMessage); mm.addMessageListener("cpows:done", recvDoneMessage); mm.addMessageListener("cpows:fail", recvFailMessage); mm.addMessageListener("cpows:parent_test", recvParentTest); mm.addMessageListener("cpows:error_reporting_test", recvErrorReportingTest); mm.addMessageListener("cpows:dom_test", recvDomTest); mm.addMessageListener("cpows:dom_test_after_gc", recvDomTestAfterGC); mm.addMessageListener("cpows:xray_test", recvXrayTest); if (typeof Symbol === "function") { mm.addMessageListener("cpows:symbol_test", recvSymbolTest); } mm.addMessageListener("cpows:compartment_test", recvCompartmentTest); mm.addMessageListener("cpows:regexp_test", recvRegExpTest); mm.addMessageListener("cpows:postmessage_test", recvPostMessageTest); mm.addMessageListener("cpows:lifetime_test_1", recvLifetimeTest1); mm.addMessageListener("cpows:lifetime_test_2", recvLifetimeTest2); mm.addMessageListener("cpows:cancel_test", recvCancelTest); mm.addMessageListener("cpows:cancel_sync_message", recvCancelSyncMessage); mm.addMessageListener("cpows:cancel_test2", recvCancelTest2); mm.addMessageListener("cpows:unsafe", recvUnsafe); mm.addMessageListener("cpows:safe", recvSafe); mm.addMessageListener("cpows:dead", recvDead); mm.loadFrameScript("chrome://mochitests/content/chrome/dom/base/test/chrome/cpows_child.js", true); } function start() { run_tests('remote'); } function finish() { ok(gReceivedErrorProbe, "Should have reported error probe"); opener.setTimeout("done()", 0); window.close(); } ]]></script> <browser type="content" src="about:blank" id="cpowbrowser_remote" remote="true"/> <browser type="content" src="about:blank" id="cpowbrowser_inprocess"/> </window>