dump('loaded child cpow test\n'); var Cu = Components.utils; var Ci = Components.interfaces; (function start() { [is_remote] = sendRpcMessage("cpows:is_remote"); var tests = [ parent_test, error_reporting_test, dom_test, xray_test, symbol_test, compartment_test, regexp_test, postmessage_test, sync_test, async_test, rpc_test, lifetime_test, cancel_test, cancel_test2, dead_test, unsafe_test, ]; function go() { if (tests.length == 0) { sendRpcMessage("cpows:done", {}); return; } var test = tests[0]; tests.shift(); test(function() { go(); }); } go(); })(); function ok(condition, message) { dump('condition: ' + condition + ', ' + message + '\n'); if (!condition) { sendAsyncMessage("cpows:fail", { message: message }); throw 'failed check: ' + message; } } var sync_obj; var async_obj; function make_object() { let o = { }; o.i = 5; o.b = true; o.s = "hello"; o.x = { i: 10 }; o.f = function () { return 99; }; o.ctor = function() { this.a = 3; } // Doing anything with this Proxy will throw. var throwing = new Proxy({}, new Proxy({}, { get: function (trap) { throw trap; } })); let array = [1, 2, 3]; let for_json = { "n": 3, "a": array, "s": "hello", o: { "x": 10 } }; let proto = { data: 42 }; let with_proto = Object.create(proto); let with_null_proto = Object.create(null); content.document.title = "Hello, Kitty"; return { "data": o, "throwing": throwing, "document": content.document, "array": array, "for_json": for_json, "with_proto": with_proto, "with_null_proto": with_null_proto }; } function make_json() { return { check: "ok" }; } function parent_test(finish) { function f(check_func) { // Make sure this doesn't crash. let array = new Uint32Array(10); content.crypto.getRandomValues(array); let result = check_func(10); ok(result == 20, "calling function in parent worked"); return result; } addMessageListener("cpows:from_parent", (msg) => { let obj = msg.objects.obj; ok(obj.a == 1, "correct value from parent"); // Test that a CPOW reference to a function in the chrome process // is callable from unprivileged content. Greasemonkey uses this // functionality. let func = msg.objects.func; let sb = Cu.Sandbox('http://www.example.com', {}); sb.func = func; ok(sb.eval('func()') == 101, "can call parent's function in child"); finish(); }); sendRpcMessage("cpows:parent_test", {}, {func: f}); } function error_reporting_test(finish) { sendRpcMessage("cpows:error_reporting_test", {}, {}); finish(); } function dom_test(finish) { let element = content.document.createElement("div"); element.id = "it_works"; content.document.body.appendChild(element); sendRpcMessage("cpows:dom_test", {}, {element: element}); Components.utils.schedulePreciseGC(function() { sendRpcMessage("cpows:dom_test_after_gc"); finish(); }); } function xray_test(finish) { let element = content.document.createElement("div"); element.wrappedJSObject.foo = "hello"; sendRpcMessage("cpows:xray_test", {}, {element: element}); finish(); } function symbol_test(finish) { let iterator = Symbol.iterator; let named = Symbol.for("cpow-test"); let object = { [iterator]: iterator, [named]: named, }; let test = ['a']; sendRpcMessage("cpows:symbol_test", {}, {object: object, test: test}); finish(); } // Parent->Child references should go X->parent.privilegedJunkScope->child.privilegedJunkScope->Y // Child->Parent references should go X->child.privilegedJunkScope->parent.unprivilegedJunkScope->Y function compartment_test(finish) { // This test primarily checks various compartment invariants for CPOWs, and // doesn't make sense to run in-process. if (!is_remote) { finish(); return; } let sb = Cu.Sandbox('http://www.example.com', { wantGlobalProperties: ['XMLHttpRequest'] }); sb.eval('function getUnprivilegedObject() { var xhr = new XMLHttpRequest(); xhr.expando = 42; return xhr; }'); function testParentObject(obj) { let results = []; function is(a, b, msg) { results.push({ result: a === b ? "PASS" : "FAIL", message: msg }) }; function ok(x, msg) { results.push({ result: x ? "PASS" : "FAIL", message: msg }) }; let cpowLocation = Cu.getCompartmentLocation(obj); ok(/Privileged Junk/.test(cpowLocation), "child->parent CPOWs should live in the privileged junk scope: " + cpowLocation); is(obj(), 42, "child->parent CPOW is invokable"); try { obj.expando; ok(false, "child->parent CPOW cannot access properties"); } catch (e) { ok(true, "child->parent CPOW cannot access properties"); } return results; } sendRpcMessage("cpows:compartment_test", {}, { getUnprivilegedObject: sb.getUnprivilegedObject, testParentObject: testParentObject }); finish(); } function regexp_test(finish) { sendRpcMessage("cpows:regexp_test", {}, { regexp: /myRegExp/g }); finish(); } function postmessage_test(finish) { sendRpcMessage("cpows:postmessage_test", {}, { win: content.window }); finish(); } function sync_test(finish) { dump('beginning cpow sync test\n'); sync_obj = make_object(); sendRpcMessage("cpows:sync", make_json(), make_object()); finish(); } function async_test(finish) { dump('beginning cpow async test\n'); async_obj = make_object(); sendAsyncMessage("cpows:async", make_json(), async_obj); addMessageListener("cpows:async_done", finish); } var rpc_obj; function rpc_test(finish) { dump('beginning cpow rpc test\n'); rpc_obj = make_object(); rpc_obj.data.reenter = function () { sendRpcMessage("cpows:reenter", { }, { data: { valid: true } }); return "ok"; } sendRpcMessage("cpows:rpc", make_json(), rpc_obj); finish(); } function lifetime_test(finish) { if (!is_remote) { // Only run this test when running out-of-process. Otherwise it // will fail, since local CPOWs don't follow the same ownership // rules. finish(); return; } dump("beginning lifetime test\n"); var obj = {"will_die": {"f": 1}}; let [result] = sendRpcMessage("cpows:lifetime_test_1", {}, {obj: obj}); ok(result == 10, "got sync result"); ok(obj.wont_die.f == 2, "got reverse CPOW"); obj.will_die = null; Components.utils.schedulePreciseGC(function() { addMessageListener("cpows:lifetime_test_3", (msg) => { ok(obj.wont_die.f == 2, "reverse CPOW still works"); finish(); }); sendRpcMessage("cpows:lifetime_test_2"); }); } function cancel_test(finish) { if (!is_remote) { // No point in doing this in single-process mode. finish(); return; } let fin1 = false, fin2 = false; // CPOW from the parent runs f. When it sends a sync message, the // CPOW is canceled. The parent starts running again immediately // after the CPOW is canceled; f also continues running. function f() { let res = sendSyncMessage("cpows:cancel_sync_message"); ok(res[0] == 12, "cancel_sync_message result correct"); fin1 = true; if (fin1 && fin2) finish(); } sendAsyncMessage("cpows:cancel_test", null, {f: f}); addMessageListener("cpows:cancel_test_done", msg => { fin2 = true; if (fin1 && fin2) finish(); }); } function cancel_test2(finish) { if (!is_remote) { // No point in doing this in single-process mode. finish(); return; } let fin1 = false, fin2 = false; // CPOW from the parent runs f. When it does a sync XHR, the // CPOW is canceled. The parent starts running again immediately // after the CPOW is canceled; f also continues running. function f() { let req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]. createInstance(Components.interfaces.nsIXMLHttpRequest); let fin = false; let reqListener = () => { if (req.readyState != req.DONE) { return; } ok(req.status == 200, "XHR succeeded"); fin = true; }; req.onload = reqListener; req.open("get", "http://example.com", false); req.send(null); ok(fin == true, "XHR happened"); fin1 = true; if (fin1 && fin2) finish(); } sendAsyncMessage("cpows:cancel_test2", null, {f: f}); addMessageListener("cpows:cancel_test2_done", msg => { fin2 = true; if (fin1 && fin2) finish(); }); } function unsafe_test(finish) { if (!is_remote) { // Only run this test when running out-of-process. finish(); return; } function f() {} sendAsyncMessage("cpows:unsafe", null, {f}); addMessageListener("cpows:unsafe_done", msg => { sendRpcMessage("cpows:safe", null, {f}); addMessageListener("cpows:safe_done", finish); }); } function dead_test(finish) { if (!is_remote) { // Only run this test when running out-of-process. finish(); return; } let gcTrigger = function() { // Force the GC to dead-ify the thing. content.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils) .garbageCollect(); } { let thing = { value: "Gonna croak" }; sendAsyncMessage("cpows:dead", null, { thing, gcTrigger }); } addMessageListener("cpows:dead_done", finish); }