Bug 1609128 - Implement polymorphism in protocol.js r=jdescottes

Differential Revision: https://phabricator.services.mozilla.com/D59333

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Alexandre Poirot 2020-01-16 16:47:01 +00:00
Родитель 2d3e4c8ba0
Коммит 8631fc037c
4 изменённых файлов: 199 добавлений и 0 удалений

Просмотреть файл

@ -21,6 +21,8 @@ function simpleHello() {
// 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",
@ -200,6 +202,15 @@ class ChildFront extends protocol.FrontClassWithSpec(childSpec) {
}
protocol.registerFront(ChildFront);
const otherChildSpec = protocol.generateActorSpec({
typeName: "otherChildActor",
methods: {},
events: {},
});
const OtherChildActor = protocol.ActorClassWithSpec(otherChildSpec, {});
class OtherChildFront extends protocol.FrontClassWithSpec(otherChildSpec) {}
protocol.registerFront(OtherChildFront);
types.addDictType("manyChildrenDict", {
child5: "childActor",
more: "array:childActor",
@ -230,6 +241,17 @@ const rootSpec = protocol.generateActorSpec({
request: { id: Arg(0) },
response: { child: RetVal("temp:childActor") },
},
getPolymorphism: {
request: { id: Arg(0, "number") },
response: { child: RetVal("polytype") },
},
requestPolymorphism: {
request: {
id: Arg(0, "number"),
actor: Arg(1, "polytype"),
},
response: { child: RetVal("polytype") },
},
clearTemporaryChildren: {},
},
});
@ -296,6 +318,24 @@ const RootActor = protocol.ActorClassWithSpec(rootSpec, {
this._temporaryHolder.destroy();
delete this._temporaryHolder;
},
getPolymorphism: function(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: function(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) {
@ -363,6 +403,7 @@ add_task(async function() {
await testEvents(trace);
await testManyChildren(trace);
await testGenerator(trace);
await testPolymorphism(trace);
await client.close();
});
@ -641,3 +682,33 @@ async function testGenerator(trace) {
Assert.ok(ret[1] !== childFront);
Assert.ok(ret[1] instanceof ChildFront);
}
async function testPolymorphism(trace) {
// 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'/);
}

Просмотреть файл

@ -0,0 +1,65 @@
"use strict";
const { types } = require("devtools/shared/protocol");
function run_test() {
types.addActorType("myActor1");
types.addActorType("myActor2");
types.addActorType("myActor3");
types.addPolymorphicType("ptype1", ["myActor1", "myActor2"]);
const ptype1 = types.getType("ptype1");
Assert.equal(ptype1.name, "ptype1");
Assert.equal(ptype1.category, "polymorphic");
types.addPolymorphicType("ptype2", ["myActor1", "myActor2", "myActor3"]);
const ptype2 = types.getType("ptype2");
Assert.equal(ptype2.name, "ptype2");
Assert.equal(ptype2.category, "polymorphic");
// Polymorphic types only accept actor types
try {
types.addPolymorphicType("ptype", ["myActor1", "myActor4"]);
Assert.ok(false, "getType should fail");
} catch (ex) {
Assert.equal(ex.toString(), "Error: Unknown type: myActor4");
}
try {
types.addPolymorphicType("ptype", ["myActor1", "string"]);
Assert.ok(false, "getType should fail");
} catch (ex) {
Assert.equal(
ex.toString(),
"Error: In polymorphic type 'myActor1,string', the type 'string' isn't an actor"
);
}
try {
types.addPolymorphicType("ptype", ["myActor1", "boolean"]);
Assert.ok(false, "getType should fail");
} catch (ex) {
Assert.equal(
ex.toString(),
"Error: In polymorphic type 'myActor1,boolean', the type 'boolean' isn't an actor"
);
}
// Polymorphic types are not compatible with array or nullables
try {
types.addPolymorphicType("ptype", ["array:myActor1", "myActor2"]);
Assert.ok(false, "addType should fail");
} catch (ex) {
Assert.equal(
ex.toString(),
"Error: In polymorphic type 'array:myActor1,myActor2', the type 'array:myActor1' isn't an actor"
);
}
try {
types.addPolymorphicType("ptype", ["nullable:myActor1", "myActor2"]);
Assert.ok(false, "addType should fail");
} catch (ex) {
Assert.equal(
ex.toString(),
"Error: In polymorphic type 'nullable:myActor1,myActor2', the type 'nullable:myActor1' isn't an actor"
);
}
}

Просмотреть файл

@ -12,5 +12,6 @@ support-files =
[test_protocol_longstring.js]
[test_protocol_simple.js]
[test_protocol_stack.js]
[test_protocol_types.js]
[test_protocol_unregister.js]
[test_protocol_watchFronts.js]

Просмотреть файл

@ -370,6 +370,68 @@ types.addActorType = function(name) {
return type;
};
types.addPolymorphicType = function(name, subtypes) {
// Assert that all subtypes are actors, as the marshalling implementation depends on that.
for (const subTypeName of subtypes) {
const subtype = types.getType(subTypeName);
if (subtype.category != "actor") {
throw new Error(
`In polymorphic type '${subtypes.join(
","
)}', the type '${subTypeName}' isn't an actor`
);
}
}
return types.addType(name, {
category: "polymorphic",
read: (value, ctx) => {
// `value` is either a string which is an Actor ID or a form object
// where `actor` is an actor ID
const actorID = typeof value === "string" ? value : value.actor;
if (!actorID) {
throw new Error(
`Was expecting one of these actors '${subtypes}' but instead got value: '${value}'`
);
}
// Extract the typeName out of the actor ID, which should be composed like this
// ${DebuggerServerConnectionPrefix}.${typeName}${Number}
const typeName = actorID.match(/\.([a-zA-Z]+)\d+$/)[1];
if (!subtypes.includes(typeName)) {
throw new Error(
`Was expecting one of these actors '${subtypes}' but instead got an actor of type: '${typeName}'`
);
}
const subtype = types.getType(typeName);
return subtype.read(value, ctx);
},
write: (value, ctx) => {
if (!value) {
throw new Error(
`Was expecting one of these actors '${subtypes}' but instead got an empty value.`
);
}
// value is either an `Actor` or a `Front` and both classes exposes a `typeName`
const typeName = value.typeName;
if (!typeName) {
throw new Error(
`Was expecting one of these actors '${subtypes}' but instead got value: '${value}'. Did you pass a form instead of an Actor?`
);
}
if (!subtypes.includes(typeName)) {
throw new Error(
`Was expecting one of these actors '${subtypes}' but instead got an actor of type: '${typeName}'`
);
}
const subtype = types.getType(typeName);
return subtype.write(value, ctx);
},
});
};
types.addNullableType = function(subtype) {
subtype = types.getType(subtype);
return types.addType("nullable:" + subtype.name, {