/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Test simple requests using the protocol helpers. */ var protocol = require("devtools/shared/protocol"); var {preEvent, types, Arg, Option, RetVal} = protocol; var events = require("sdk/event/core"); function simpleHello() { return { from: "root", applicationType: "xpcshell-tests", traits: [], }; } var testTypes = {}; // Predeclaring the actor type so that it can be used in the // implementation of the child actor. types.addActorType("childActor"); const childSpec = protocol.generateActorSpec({ typeName: "childActor", events: { "event1" : { a: Arg(0), b: Arg(1), c: Arg(2) }, "event2" : { a: Arg(0), b: Arg(1), c: Arg(2) }, "named-event": { type: "namedEvent", a: Arg(0), b: Arg(1), c: Arg(2) }, "object-event": { type: "objectEvent", detail: Arg(0, "childActor#detail1"), }, "array-object-event": { type: "arrayObjectEvent", detail: Arg(0, "array:childActor#detail2"), } }, methods: { echo: { request: { str: Arg(0) }, response: { str: RetVal("string") }, }, getDetail1: { // This also exercises return-value-as-packet. response: RetVal("childActor#detail1"), }, getDetail2: { // This also exercises return-value-as-packet. response: RetVal("childActor#detail2"), }, getIDDetail: { response: { idDetail: RetVal("childActor#actorid") } }, getIntArray: { request: { inputArray: Arg(0, "array:number") }, response: RetVal("array:number") }, getSibling: { request: { id: Arg(0) }, response: { sibling: RetVal("childActor") } }, emitEvents: { response: { value: "correct response" }, }, release: { release: true } } }); var ChildActor = protocol.ActorClassWithSpec(childSpec, { // Actors returned by this actor should be owned by the root actor. marshallPool: function () { return this.parent(); }, toString: function () { return "[ChildActor " + this.childID + "]"; }, initialize: function (conn, id) { protocol.Actor.prototype.initialize.call(this, conn); this.childID = id; }, destroy: function () { protocol.Actor.prototype.destroy.call(this); this.destroyed = true; }, form: function (detail) { if (detail === "actorid") { return this.actorID; } return { actor: this.actorID, childID: this.childID, detail: detail }; }, echo: function (str) { return str; }, getDetail1: function () { return this; }, getDetail2: function () { return this; }, getIDDetail: function () { return this; }, getIntArray: function (inputArray) { // Test that protocol.js converts an iterator to an array. let f = function* () { for (let i of inputArray) { yield 2 * i; } }; return f(); }, getSibling: function (id) { return this.parent().getChild(id); }, emitEvents: function () { events.emit(this, "event1", 1, 2, 3); events.emit(this, "event2", 4, 5, 6); events.emit(this, "named-event", 1, 2, 3); events.emit(this, "object-event", this); events.emit(this, "array-object-event", [this]); }, release: function () { }, }); var ChildFront = protocol.FrontClassWithSpec(childSpec, { initialize: function (client, form) { protocol.Front.prototype.initialize.call(this, client, form); }, destroy: function () { this.destroyed = true; protocol.Front.prototype.destroy.call(this); }, marshallPool: function () { return this.parent(); }, toString: function () { return "[child front " + this.childID + "]"; }, form: function (form, detail) { if (detail === "actorid") { return; } this.childID = form.childID; this.detail = form.detail; }, onEvent1: preEvent("event1", function (a, b, c) { this.event1arg3 = c; }), onEvent2a: preEvent("event2", function (a, b, c) { return promise.resolve().then(() => this.event2arg3 = c); }), onEvent2b: preEvent("event2", function (a, b, c) { this.event2arg2 = b; }), }); types.addDictType("manyChildrenDict", { child5: "childActor", more: "array:childActor", }); types.addLifetime("temp", "_temporaryHolder"); const rootSpec = protocol.generateActorSpec({ typeName: "root", methods: { getChild: { request: { str: Arg(0) }, response: { actor: RetVal("childActor") }, }, getChildren: { request: { ids: Arg(0, "array:string") }, response: { children: RetVal("array:childActor") }, }, getChildren2: { request: { ids: Arg(0, "array:childActor") }, response: { children: RetVal("array:childActor") }, }, getManyChildren: { response: RetVal("manyChildrenDict") }, getTemporaryChild: { request: { id: Arg(0) }, response: { child: RetVal("temp:childActor") } }, clearTemporaryChildren: {} } }); var rootActor = null; var RootActor = protocol.ActorClassWithSpec(rootSpec, { toString: function () { return "[root actor]"; }, initialize: function (conn) { rootActor = this; this.actorID = "root"; this._children = {}; protocol.Actor.prototype.initialize.call(this, conn); // Root actor owns itself. this.manage(this); }, sayHello: simpleHello, getChild: function (id) { if (id in this._children) { return this._children[id]; } let child = new ChildActor(this.conn, id); this._children[id] = child; return child; }, getChildren: function (ids) { return ids.map(id => this.getChild(id)); }, getChildren2: function (ids) { let f = function* () { for (let c of ids) { yield c; } }; return f(); }, getManyChildren: function () { return { foo: "bar", // note that this isn't in the specialization array. child5: this.getChild("child5"), more: [ this.getChild("child6"), this.getChild("child7") ] }; }, // This should remind you of a pause actor. getTemporaryChild: function (id) { if (!this._temporaryHolder) { this._temporaryHolder = this.manage(new protocol.Actor(this.conn)); } return new ChildActor(this.conn, id); }, clearTemporaryChildren: function (id) { if (!this._temporaryHolder) { return; } this._temporaryHolder.destroy(); delete this._temporaryHolder; } }); var RootFront = protocol.FrontClassWithSpec(rootSpec, { toString: function () { return "[root front]"; }, initialize: function (client) { this.actorID = "root"; protocol.Front.prototype.initialize.call(this, client); // Root actor owns itself. this.manage(this); }, getTemporaryChild: protocol.custom(function (id) { if (!this._temporaryHolder) { this._temporaryHolder = protocol.Front(this.conn); this._temporaryHolder.actorID = this.actorID + "_temp"; this._temporaryHolder = this.manage(this._temporaryHolder); } return this._getTemporaryChild(id); }, { impl: "_getTemporaryChild" }), clearTemporaryChildren: protocol.custom(function () { if (!this._temporaryHolder) { return promise.resolve(undefined); } this._temporaryHolder.destroy(); delete this._temporaryHolder; return this._clearTemporaryChildren(); }, { impl: "_clearTemporaryChildren" }) }); function run_test() { DebuggerServer.createRootActor = (conn => { return RootActor(conn); }); DebuggerServer.init(); let trace = connectPipeTracing(); let client = new DebuggerClient(trace); client.connect().then(([applicationType, traits]) => { trace.expectReceive({"from":"", "applicationType":"xpcshell-tests", "traits":[]}); do_check_eq(applicationType, "xpcshell-tests"); let rootFront = RootFront(client); let childFront = null; let expectRootChildren = size => { do_check_eq(rootActor._poolMap.size, size + 1); do_check_eq(rootFront._poolMap.size, size + 1); if (childFront) { do_check_eq(childFront._poolMap.size, 0); } }; rootFront.getChild("child1").then(ret => { trace.expectSend({"type":"getChild", "str":"child1", "to":""}); trace.expectReceive({"actor":"", "from":""}); childFront = ret; do_check_true(childFront instanceof ChildFront); do_check_eq(childFront.childID, "child1"); expectRootChildren(1); }).then(() => { // Request the child again, make sure the same is returned. return rootFront.getChild("child1"); }).then(ret => { trace.expectSend({"type":"getChild", "str":"child1", "to":""}); trace.expectReceive({"actor":"", "from":""}); expectRootChildren(1); do_check_true(ret === childFront); }).then(() => { return childFront.echo("hello"); }).then(ret => { trace.expectSend({"type":"echo", "str":"hello", "to":""}); trace.expectReceive({"str":"hello", "from":""}); do_check_eq(ret, "hello"); }).then(() => { return childFront.getDetail1(); }).then(ret => { trace.expectSend({"type":"getDetail1", "to":""}); trace.expectReceive({"actor":"", "childID":"child1", "detail":"detail1", "from":""}); do_check_true(ret === childFront); do_check_eq(childFront.detail, "detail1"); }).then(() => { return childFront.getDetail2(); }).then(ret => { trace.expectSend({"type":"getDetail2", "to":""}); trace.expectReceive({"actor":"", "childID":"child1", "detail":"detail2", "from":""}); do_check_true(ret === childFront); do_check_eq(childFront.detail, "detail2"); }).then(() => { return childFront.getIDDetail(); }).then(ret => { trace.expectSend({"type":"getIDDetail", "to":""}); trace.expectReceive({"idDetail": childFront.actorID, "from":""}); do_check_true(ret === childFront); }).then(() => { return childFront.getSibling("siblingID"); }).then(ret => { trace.expectSend({"type":"getSibling", "id":"siblingID", "to":""}); trace.expectReceive({"sibling":{"actor":"", "childID":"siblingID"}, "from":""}); expectRootChildren(2); }).then(ret => { return rootFront.getTemporaryChild("temp1").then(temp1 => { trace.expectSend({"type":"getTemporaryChild", "id":"temp1", "to":""}); trace.expectReceive({"child":{"actor":"", "childID":"temp1"}, "from":""}); // At this point we expect two direct children, plus the temporary holder // which should hold 1 itself. do_check_eq(rootActor._temporaryHolder.__poolMap.size, 1); do_check_eq(rootFront._temporaryHolder.__poolMap.size, 1); expectRootChildren(3); return rootFront.getTemporaryChild("temp2").then(temp2 => { trace.expectSend({"type":"getTemporaryChild", "id":"temp2", "to":""}); trace.expectReceive({"child":{"actor":"", "childID":"temp2"}, "from":""}); // Same amount of direct children, and an extra in the temporary holder. expectRootChildren(3); do_check_eq(rootActor._temporaryHolder.__poolMap.size, 2); do_check_eq(rootFront._temporaryHolder.__poolMap.size, 2); // Get the children of the temporary holder... let checkActors = rootActor._temporaryHolder.__poolMap.values(); let checkFronts = rootFront._temporaryHolder.__poolMap.values(); // Now release the temporary holders and expect them to drop again. return rootFront.clearTemporaryChildren().then(() => { trace.expectSend({"type":"clearTemporaryChildren", "to":""}); trace.expectReceive({"from":""}); expectRootChildren(2); do_check_false(!!rootActor._temporaryHolder); do_check_false(!!rootFront._temporaryHolder); for (let checkActor of checkActors) { do_check_true(checkActor.destroyed); do_check_true(checkActor.destroyed); } }); }); }); }).then(ret => { return rootFront.getChildren(["child1", "child2"]); }).then(ret => { trace.expectSend({"type":"getChildren", "ids":["child1", "child2"], "to":""}); trace.expectReceive({"children":[{"actor":"", "childID":"child1"}, {"actor":"", "childID":"child2"}], "from":""}); expectRootChildren(3); do_check_true(ret[0] === childFront); do_check_true(ret[1] !== childFront); do_check_true(ret[1] instanceof ChildFront); // On both children, listen to events. We're only // going to trigger events on the first child, so an event // triggered on the second should cause immediate failures. let set = new Set(["event1", "event2", "named-event", "object-event", "array-object-event"]); childFront.on("event1", (a, b, c) => { do_check_eq(a, 1); do_check_eq(b, 2); do_check_eq(c, 3); // Verify that the pre-event handler was called. do_check_eq(childFront.event1arg3, 3); set.delete("event1"); }); childFront.on("event2", (a, b, c) => { do_check_eq(a, 4); do_check_eq(b, 5); do_check_eq(c, 6); // Verify that the async pre-event handler was called, // setting the property before this handler was called. do_check_eq(childFront.event2arg3, 6); // And check that the sync preEvent with the same name is also // executed do_check_eq(childFront.event2arg2, 5); set.delete("event2"); }); childFront.on("named-event", (a, b, c) => { do_check_eq(a, 1); do_check_eq(b, 2); do_check_eq(c, 3); set.delete("named-event"); }); childFront.on("object-event", (obj) => { do_check_true(obj === childFront); do_check_eq(childFront.detail, "detail1"); set.delete("object-event"); }); childFront.on("array-object-event", (array) => { do_check_true(array[0] === childFront); do_check_eq(childFront.detail, "detail2"); set.delete("array-object-event"); }); let fail = function () { do_throw("Unexpected event"); }; ret[1].on("event1", fail); ret[1].on("event2", fail); ret[1].on("named-event", fail); ret[1].on("object-event", fail); ret[1].on("array-object-event", fail); return childFront.emitEvents().then(() => { trace.expectSend({"type":"emitEvents", "to":""}); trace.expectReceive({"type":"event1", "a":1, "b":2, "c":3, "from":""}); trace.expectReceive({"type":"event2", "a":4, "b":5, "c":6, "from":""}); trace.expectReceive({"type":"namedEvent", "a":1, "b":2, "c":3, "from":""}); trace.expectReceive({"type":"objectEvent", "detail":{"actor":"", "childID":"child1", "detail":"detail1"}, "from":""}); trace.expectReceive({"type":"arrayObjectEvent", "detail":[{"actor":"", "childID":"child1", "detail":"detail2"}], "from":""}); trace.expectReceive({"value":"correct response", "from":""}); do_check_eq(set.size, 0); }); }).then(ret => { return rootFront.getManyChildren(); }).then(ret => { trace.expectSend({"type":"getManyChildren", "to":""}); trace.expectReceive({"foo":"bar", "child5":{"actor":"", "childID":"child5"}, "more":[{"actor":"", "childID":"child6"}, {"actor":"", "childID":"child7"}], "from":""}); // Check all the crazy stuff we did in getManyChildren do_check_eq(ret.foo, "bar"); do_check_eq(ret.child5.childID, "child5"); do_check_eq(ret.more[0].childID, "child6"); do_check_eq(ret.more[1].childID, "child7"); }).then(() => { // Test accepting a generator. let f = function* () { for (let i of [1, 2, 3, 4, 5]) { yield i; } }; return childFront.getIntArray(f()); }).then((ret) => { do_check_eq(ret.length, 5); let expected = [2, 4, 6, 8, 10]; for (let i = 0; i < 5; ++i) { do_check_eq(ret[i], expected[i]); } }).then(() => { return rootFront.getChildren(["child1", "child2"]); }).then(ids => { let f = function* () { for (let id of ids) { yield id; } }; return rootFront.getChildren2(f()); }).then(ret => { do_check_eq(ret.length, 2); do_check_true(ret[0] === childFront); do_check_true(ret[1] !== childFront); do_check_true(ret[1] instanceof ChildFront); }).then(() => { client.close().then(() => { do_test_finished(); }); }).then(null, err => { do_report_unexpected_exception(err, "Failure executing test"); }); }); do_test_pending(); }