Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
/* eslint-disable max-nested-callbacks */
"use strict";
/**
* Test simple requests using the protocol helpers.
*/
const protocol = require("resource://devtools/shared/protocol.js");
const { types, Arg, RetVal } = protocol;
// Predeclaring the actor type so that it can be used in the
// implementation of the child actor.
types.addActorType("childActor");
types.addActorType("otherChildActor");
types.addPolymorphicType("polytype", ["childActor", "otherChildActor"]);
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#actorid"),
},
"array-object-event": {
type: "arrayObjectEvent",
detail: Arg(0, "array:childActor#actorid"),
},
},
methods: {
echo: {
request: { str: Arg(0) },
response: { str: RetVal("string") },
},
getDetail1: {
response: {
child: RetVal("childActor#actorid"),
},
},
getDetail2: {
response: {
child: RetVal("childActor#actorid"),
},
},
getIDDetail: {
response: {
idDetail: RetVal("childActor#actorid"),
},
},
getIntArray: {
request: { inputArray: Arg(0, "array:number") },
response: {
intArray: RetVal("array:number"),
},
},
getSibling: {
request: { id: Arg(0) },
response: { sibling: RetVal("childActor") },
},
emitEvents: {
response: { value: RetVal("string") },
},
release: {
release: true,
},
},
});
class ChildActor extends protocol.Actor {
constructor(conn, id) {
super(conn, childSpec);
this.childID = id;
}
// Actors returned by this actor should be owned by the root actor.
marshallPool() {
return this.getParent();
}
toString() {
return "[ChildActor " + this.childID + "]";
}
destroy() {
super.destroy();
this.destroyed = true;
}
form() {
return {
actor: this.actorID,
childID: this.childID,
};
}
echo(str) {
return str;
}
getDetail1() {
return this;
}
getDetail2() {
return this;
}
getIDDetail() {
return this;
}
getIntArray(inputArray) {
// Test that protocol.js converts an iterator to an array.
const f = function* () {
for (const i of inputArray) {
yield 2 * i;
}
};
return f();
}
getSibling(id) {
return this.getParent().getChild(id);
}
emitEvents() {
this.emit("event1", 1, 2, 3);
this.emit("event2", 4, 5, 6);
this.emit("named-event", 1, 2, 3);
this.emit("object-event", this);
this.emit("array-object-event", [this]);
return "correct response";
}
release() {}
}
class ChildFront extends protocol.FrontClassWithSpec(childSpec) {
constructor(client, targetFront, parentFront) {
super(client, targetFront, parentFront);
this._parentFront = parentFront;
this.before("event1", this.onEvent1.bind(this));
this.before("event2", this.onEvent2a.bind(this));
this.on("event2", this.onEvent2b.bind(this));
}
destroy() {
this.destroyed = true;
// Call parent's destroy, which may be re-entrant and recall this function
this._parentFront.destroy();
super.destroy();
}
marshallPool() {
return this.getParent();
}
toString() {
return "[child front " + this.childID + "]";
}
form(form) {
this.childID = form.childID;
}
onEvent1(a, b, c) {
this.event1arg3 = c;
}
onEvent2a(a, b, c) {
return Promise.resolve().then(() => {
this.event2arg3 = c;
});
}
onEvent2b(a, b) {
this.event2arg2 = b;
}
}
protocol.registerFront(ChildFront);
const otherChildSpec = protocol.generateActorSpec({
typeName: "otherChildActor",
methods: {
getOtherChild: {
request: {},
response: { sibling: RetVal("otherChildActor") },
},
},
events: {},
});
class OtherChildActor extends protocol.Actor {
constructor(conn) {
super(conn, otherChildSpec);
}
getOtherChild() {
return new OtherChildActor(this.conn);
}
}
class OtherChildFront extends protocol.FrontClassWithSpec(otherChildSpec) {}
protocol.registerFront(OtherChildFront);
types.addDictType("manyChildrenDict", {
child5: "childActor",
more: "array:childActor",
});
const rootSpec = protocol.generateActorSpec({
typeName: "root",
methods: {
getChild: {
request: { str: Arg(0) },
response: { actor: RetVal("childActor") },
},
getOtherChild: {
request: {},
response: { sibling: RetVal("otherChildActor") },
},
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"),
},
getPolymorphism: {
request: { id: Arg(0, "number") },
response: { child: RetVal("polytype") },
},
requestPolymorphism: {
request: {
id: Arg(0, "number"),
actor: Arg(1, "polytype"),
},
response: { child: RetVal("polytype") },
},
},
});
let rootActor = null;
class RootActor extends protocol.Actor {
constructor(conn) {
super(conn, rootSpec);
rootActor = this;
this.actorID = "root";
this._children = {};
}
toString() {
return "[root actor]";
}
sayHello() {
return {
from: "root",
applicationType: "xpcshell-tests",
traits: [],
};
}
getChild(id) {
if (id in this._children) {
return this._children[id];
}
const child = new ChildActor(this.conn, id);
this._children[id] = child;
return child;
}
// Other child actor won't all be own by the root actor
// and can have their own children
getOtherChild() {
return new OtherChildActor(this.conn);
}
getChildren(ids) {
return ids.map(id => this.getChild(id));
}
getChildren2(ids) {
const f = function* () {
for (const c of ids) {
yield c;
}
};
return f();
}
getManyChildren() {
return {
// note that this isn't in the specialization array.
foo: "bar",
child5: this.getChild("child5"),
more: [this.getChild("child6"), this.getChild("child7")],
};
}
getPolymorphism(id) {
if (id == 0) {
return new ChildActor(this.conn, id);
} else if (id == 1) {
return new OtherChildActor(this.conn);
}
throw new Error("Unexpected id");
}
requestPolymorphism(id, actor) {
if (id == 0 && actor instanceof ChildActor) {
return actor;
} else if (id == 1 && actor instanceof OtherChildActor) {
return actor;
}
throw new Error("Unexpected id or actor");
}
}
class RootFront extends protocol.FrontClassWithSpec(rootSpec) {
constructor(client, targetFront, parentFront) {
super(client, targetFront, parentFront);
this.actorID = "root";
// Root actor owns itself.
this.manage(this);
}
toString() {
return "[root front]";
}
}
let rootFront, childFront;
function expectRootChildren(size) {
Assert.equal(rootActor._poolMap.size, size);
Assert.equal(rootFront._poolMap.size, size + 1);
if (childFront) {
Assert.equal(childFront._poolMap.size, 0);
}
}
protocol.registerFront(RootFront);
function childrenOfType(pool, type) {
const children = [...rootFront.poolChildren()];
return children.filter(child => child instanceof type);
}
add_task(async function () {
DevToolsServer.createRootActor = conn => {
return new RootActor(conn);
};
DevToolsServer.init();
const trace = connectPipeTracing();
const client = new DevToolsClient(trace);
const [applicationType] = await client.connect();
trace.expectReceive({
from: "<actorid>",
applicationType: "xpcshell-tests",
traits: [],
});
Assert.equal(applicationType, "xpcshell-tests");
rootFront = client.mainRoot;
await testSimpleChildren(trace);
await testDetail(trace);
await testSibling(trace);
await testEvents(trace);
await testManyChildren(trace);
await testGenerator(trace);
await testPolymorphism(trace);
await testUnmanageChildren(trace);
// Execute that assertion very last as it destroy the root front and actor
await testDestroy(trace);
await client.close();
});
async function testSimpleChildren(trace) {
childFront = await rootFront.getChild("child1");
trace.expectSend({ type: "getChild", str: "child1", to: "<actorid>" });
trace.expectReceive({ actor: "<actorid>", from: "<actorid>" });
Assert.ok(childFront instanceof ChildFront);
Assert.equal(childFront.childID, "child1");
expectRootChildren(1);
// Request the child again, make sure the same is returned.
let ret = await rootFront.getChild("child1");
trace.expectSend({ type: "getChild", str: "child1", to: "<actorid>" });
trace.expectReceive({ actor: "<actorid>", from: "<actorid>" });
expectRootChildren(1);
Assert.ok(ret === childFront);
ret = await childFront.echo("hello");
trace.expectSend({ type: "echo", str: "hello", to: "<actorid>" });
trace.expectReceive({ str: "hello", from: "<actorid>" });
Assert.equal(ret, "hello");
}
async function testDetail(trace) {
let ret = await childFront.getDetail1();
trace.expectSend({ type: "getDetail1", to: "<actorid>" });
trace.expectReceive({ child: childFront.actorID, from: "<actorid>" });
Assert.ok(ret === childFront);
ret = await childFront.getDetail2();
trace.expectSend({ type: "getDetail2", to: "<actorid>" });
trace.expectReceive({ child: childFront.actorID, from: "<actorid>" });
Assert.ok(ret === childFront);
ret = await childFront.getIDDetail();
trace.expectSend({ type: "getIDDetail", to: "<actorid>" });
trace.expectReceive({
idDetail: childFront.actorID,
from: "<actorid>",
});
Assert.ok(ret === childFront);
}
async function testSibling(trace) {
await childFront.getSibling("siblingID");
trace.expectSend({
type: "getSibling",
id: "siblingID",
to: "<actorid>",
});
trace.expectReceive({
sibling: { actor: "<actorid>", childID: "siblingID" },
from: "<actorid>",
});
expectRootChildren(2);
}
async function testEvents(trace) {
const ret = await rootFront.getChildren(["child1", "child2"]);
trace.expectSend({
type: "getChildren",
ids: ["child1", "child2"],
to: "<actorid>",
});
trace.expectReceive({
children: [
{ actor: "<actorid>", childID: "child1" },
{ actor: "<actorid>", childID: "child2" },
],
from: "<actorid>",
});
expectRootChildren(3);
Assert.ok(ret[0] === childFront);
Assert.ok(ret[1] !== childFront);
Assert.ok(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.
const set = new Set([
"event1",
"event2",
"named-event",
"object-event",
"array-object-event",
]);
childFront.on("event1", (a, b, c) => {
Assert.equal(a, 1);
Assert.equal(b, 2);
Assert.equal(c, 3);
// Verify that the pre-event handler was called.
Assert.equal(childFront.event1arg3, 3);
set.delete("event1");
});
childFront.on("event2", (a, b, c) => {
Assert.equal(a, 4);
Assert.equal(b, 5);
Assert.equal(c, 6);
// Verify that the async pre-event handler was called,
// setting the property before this handler was called.
Assert.equal(childFront.event2arg3, 6);
// And check that the sync preEvent with the same name is also
// executed
Assert.equal(childFront.event2arg2, 5);
set.delete("event2");
});
childFront.on("named-event", (a, b, c) => {
Assert.equal(a, 1);
Assert.equal(b, 2);
Assert.equal(c, 3);
set.delete("named-event");
});
childFront.on("object-event", obj => {
Assert.ok(obj === childFront);
set.delete("object-event");
});
childFront.on("array-object-event", array => {
Assert.ok(array[0] === childFront);
set.delete("array-object-event");
});
const 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);
await childFront.emitEvents();
trace.expectSend({ type: "emitEvents", to: "<actorid>" });
trace.expectReceive({
type: "event1",
a: 1,
b: 2,
c: 3,
from: "<actorid>",
});
trace.expectReceive({
type: "event2",
a: 4,
b: 5,
c: 6,
from: "<actorid>",
});
trace.expectReceive({
type: "namedEvent",
a: 1,
b: 2,
c: 3,
from: "<actorid>",
});
trace.expectReceive({
type: "objectEvent",
detail: childFront.actorID,
from: "<actorid>",
});
trace.expectReceive({
type: "arrayObjectEvent",
detail: [childFront.actorID],
from: "<actorid>",
});
trace.expectReceive({ value: "correct response", from: "<actorid>" });
Assert.equal(set.size, 0);
}
async function testManyChildren(trace) {
const ret = await rootFront.getManyChildren();
trace.expectSend({ type: "getManyChildren", to: "<actorid>" });
trace.expectReceive({
foo: "bar",
child5: { actor: "<actorid>", childID: "child5" },
more: [
{ actor: "<actorid>", childID: "child6" },
{ actor: "<actorid>", childID: "child7" },
],
from: "<actorid>",
});
// Check all the crazy stuff we did in getManyChildren
Assert.equal(ret.foo, "bar");
Assert.equal(ret.child5.childID, "child5");
Assert.equal(ret.more[0].childID, "child6");
Assert.equal(ret.more[1].childID, "child7");
}
async function testGenerator() {
// Test accepting a generator.
const f = function* () {
for (const i of [1, 2, 3, 4, 5]) {
yield i;
}
};
let ret = await childFront.getIntArray(f());
Assert.equal(ret.length, 5);
const expected = [2, 4, 6, 8, 10];
for (let i = 0; i < 5; ++i) {
Assert.equal(ret[i], expected[i]);
}
const ids = await rootFront.getChildren(["child1", "child2"]);
const f2 = function* () {
for (const id of ids) {
yield id;
}
};
ret = await rootFront.getChildren2(f2());
Assert.equal(ret.length, 2);
Assert.ok(ret[0] === childFront);
Assert.ok(ret[1] !== childFront);
Assert.ok(ret[1] instanceof ChildFront);
}
async function testPolymorphism() {
// Check polymorphic types returned by an actor
const firstChild = await rootFront.getPolymorphism(0);
Assert.ok(firstChild instanceof ChildFront);
// Check polymorphic types passed to a front
const sameFirstChild = await rootFront.requestPolymorphism(0, firstChild);
Assert.ok(sameFirstChild instanceof ChildFront);
Assert.equal(sameFirstChild, firstChild);
// Same with the second possible type
const secondChild = await rootFront.getPolymorphism(1);
Assert.ok(secondChild instanceof OtherChildFront);
const sameSecondChild = await rootFront.requestPolymorphism(1, secondChild);
Assert.ok(sameSecondChild instanceof OtherChildFront);
Assert.equal(sameSecondChild, secondChild);
// Check that any other type is rejected
Assert.throws(() => {
rootFront.requestPolymorphism(0, null);
}, /Was expecting one of these actors 'childActor,otherChildActor' but instead got an empty value/);
Assert.throws(() => {
rootFront.requestPolymorphism(0, 42);
}, /Was expecting one of these actors 'childActor,otherChildActor' but instead got value: '42'/);
Assert.throws(() => {
rootFront.requestPolymorphism(0, rootFront);
}, /Was expecting one of these actors 'childActor,otherChildActor' but instead got an actor of type: 'root'/);
}
async function testUnmanageChildren() {
// There is already one front of type OtherChildFront
Assert.equal(childrenOfType(rootFront, OtherChildFront).length, 1);
// Create another front of type OtherChildFront
const front = await rootFront.getPolymorphism(1);
Assert.ok(front instanceof OtherChildFront);
Assert.equal(childrenOfType(rootFront, OtherChildFront).length, 2);
// Remove all fronts of type OtherChildFront
rootFront.unmanageChildren(OtherChildFront);
Assert.ok(
!front.isDestroyed(),
"Unmanaged front is not considered as destroyed"
);
Assert.equal(childrenOfType(rootFront, OtherChildFront).length, 0);
}
async function testDestroy() {
const front = await rootFront.getOtherChild();
const otherChildFront = await front.getOtherChild();
Assert.equal(
otherChildFront.getParent(),
front,
"the child is a children of first front"
);
front.destroy();
Assert.ok(front.isDestroyed(), "sibling is correctly reported as destroyed");
Assert.ok(!front.getParent(), "sibling has no more parent declared");
Assert.ok(otherChildFront.isDestroyed(), "the child is also destroyed");
Assert.ok(
!otherChildFront.getParent(),
"the child also has no more parent declared"
);
Assert.ok(
!otherChildFront.parentPool,
"the child also has its parentPool attribute nullified"
);
// Verify that re-entrant Front.destroy doesn't throw, nor loop
// Execute that very last as it will destroy the root actor and front
const sibling = await childFront.getSibling("siblingID");
sibling.destroy();
}