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