Bug 1072080 - Add the ability to define a marshaller for form data. r=jryans,jsantell

This commit is contained in:
Dave Camp 2014-10-11 13:08:07 -07:00
parent 6d36f49b0b
commit 2d4dc42029
6 changed files with 219 additions and 14 deletions

View File

@ -79,11 +79,8 @@ exports.ShortLongString = Class({
})
exports.LongStringFront = protocol.FrontClass(exports.LongStringActor, {
initialize: function(client, form) {
// Don't give the form by default, because we're being tricky and it might just
// be a string.
protocol.Front.prototype.initialize.call(this, client, null);
this.form(form);
initialize: function(client) {
protocol.Front.prototype.initialize.call(this, client);
},
destroy: function() {
@ -94,7 +91,7 @@ exports.LongStringFront = protocol.FrontClass(exports.LongStringActor, {
},
form: function(form) {
this.actorID = form.actorID;
this.actorID = form.actor;
this.initial = form.initial;
this.length = form.length;
},

View File

@ -298,7 +298,11 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
let AudioNodeFront = protocol.FrontClass(AudioNodeActor, {
initialize: function (client, form) {
protocol.Front.prototype.initialize.call(this, client, form);
this.manage(this);
// if we were manually passed a form, this was created manually and
// needs to own itself for now.
if (form) {
this.manage(this);
}
}
});
@ -435,6 +439,8 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
return;
}
this._initialized = false;
systemOff("webaudio-node-demise", this._onDestroyNode);
off(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
off(this.tabActor, "window-ready", this._onGlobalCreated);
this.tabActor = null;

View File

@ -145,6 +145,7 @@ types.addType = function(name, typeObject={}, options={}) {
}
let type = object.merge({
toString() { return "[protocol type:" + name + "]"},
name: name,
primitive: !(typeObject.read || typeObject.write),
read: identityWrite,
@ -258,13 +259,15 @@ types.addActorType = function(name) {
// if it isn't found.
let actorID = typeof(v) === "string" ? v : v.actor;
let front = ctx.conn.getActor(actorID);
if (front) {
front.form(v, detail, ctx);
} else {
front = new type.frontClass(ctx.conn, v, detail, ctx)
if (!front) {
front = new type.frontClass(ctx.conn);
front.actorID = actorID;
ctx.marshallPool().manage(front);
}
v = type.formType(detail).read(v, front, detail);
front.form(v, detail, ctx);
return front;
},
write: (v, ctx, detail) => {
@ -274,12 +277,28 @@ types.addActorType = function(name) {
if (!v.actorID) {
ctx.marshallPool().manage(v);
}
return v.form(detail);
return type.formType(detail).write(v.form(detail), ctx, detail);
}
// Writing a request from the client side, just send the actor id.
return v.actorID;
},
formType: (detail) => {
if (!("formType" in type.actorSpec)) {
return types.Primitive;
}
let formAttr = "formType";
if (detail) {
formAttr += "#" + detail;
}
if (!(formAttr in type.actorSpec)) {
throw new Error("No type defined for " + formAttr);
}
return type.actorSpec[formAttr];
}
}, {
// We usually freeze types, but actor types are updated when clients are
// created, so don't freeze yet.
@ -824,6 +843,8 @@ let Actor = Class({
}
},
toString: function() { return "[Actor " + this.typeName + "/" + this.actorID + "]" },
_sendEvent: function(name, ...args) {
if (!this._actorSpec.events.has(name)) {
// It's ok to emit events that don't go over the wire.
@ -908,13 +929,24 @@ let actorProto = function(actorProto) {
methods: [],
};
// Find method specifications attached to prototype properties.
// Find method and form specifications attached to prototype properties.
for (let name of Object.getOwnPropertyNames(actorProto)) {
let desc = Object.getOwnPropertyDescriptor(actorProto, name);
if (!desc.value) {
continue;
}
if (name.startsWith("formType")) {
if (typeof(desc.value) === "string") {
protoSpec[name] = types.getType(desc.value);
} else if (desc.value.name && registeredTypes.has(desc.value.name)) {
protoSpec[name] = desc.value;
} else {
// Shorthand for a newly-registered DictType.
protoSpec[name] = types.addDictType(actorProto.typeName + "__" + name, desc.value);
}
}
if (desc.value._methodSpec) {
let frozenSpec = desc.value._methodSpec;
let spec = {};
@ -1044,8 +1076,14 @@ let Front = Class({
initialize: function(conn=null, form=null, detail=null, context=null) {
Pool.prototype.initialize.call(this, conn);
this._requests = [];
// protocol.js no longer uses this data in the constructor, only external
// uses do. External usage of manually-constructed fronts will be
// drastically reduced if we convert the root and tab actors to
// protocol.js, in which case this can probably go away.
if (form) {
this.actorID = form.actor;
form = types.getType(this.typeName).formType(detail).read(form, this, detail);
this.form(form, detail, context);
}
},
@ -1120,6 +1158,7 @@ let Front = Class({
args = event.request.read(packet, this);
} catch(ex) {
console.error("Error reading event: " + packet.type);
console.exception(ex);
throw ex;
}
if (event.pre) {

View File

@ -234,7 +234,9 @@ let RootFront = protocol.FrontClass(RootActor, {
getTemporaryChild: protocol.custom(function(id) {
if (!this._temporaryHolder) {
this._temporaryHolder = this.manage(new protocol.Front(this.conn, {actor: this.actorID + "_temp"}));
this._temporaryHolder = protocol.Front(this.conn);
this._temporaryHolder.actorID = this.actorID + "_temp";
this._temporaryHolder = this.manage(this._temporaryHolder);
}
return this._getTemporaryChild(id);
},{

View File

@ -0,0 +1,160 @@
let protocol = devtools.require("devtools/server/protocol");
let {method, Arg, Option, RetVal} = protocol;
protocol.types.addActorType("child");
protocol.types.addActorType("root");
// The child actor doesn't provide a form description
let ChildActor = protocol.ActorClass({
typeName: "child",
initialize(conn) {
protocol.Actor.prototype.initialize.call(this, conn);
},
form(detail) {
return {
actor: this.actorID,
extra: "extra"
}
},
getChild: method(function() {
return this;
}, {
response: RetVal("child")
}),
});
let ChildFront = protocol.FrontClass(ChildActor, {
initialize(client) {
protocol.Front.prototype.initialize.call(this, client);
},
form(v, ctx, detail) {
this.extra = v.extra;
}
});
// The root actor does provide a form description.
let RootActor = protocol.ActorClass({
typeName: "root",
initialize(conn) {
protocol.Actor.prototype.initialize.call(this, conn);
this.manage(this);
this.child = new ChildActor();
},
// Basic form type, relies on implicit DictType creation
formType: {
childActor: "child"
},
sayHello() {
return {
from: "root",
applicationType: "xpcshell-tests",
traits: []
}
},
// This detail uses explicit DictType creation
"formType#detail1": protocol.types.addDictType("RootActorFormTypeDetail1", {
detailItem: "child"
}),
// This detail a string type.
"formType#actorid": "string",
form(detail) {
if (detail === "detail1") {
return {
actor: this.actorID,
detailItem: this.child
}
} else if (detail === "actorid") {
return this.actorID;
}
return {
actor: this.actorID,
childActor: this.child
}
},
getDefault: method(function() {
return this;
}, {
response: RetVal("root")
}),
getDetail1: method(function() {
return this;
}, {
response: RetVal("root#detail1")
}),
getDetail2: method(function() {
return this;
}, {
response: {
item: RetVal("root#actorid")
}
}),
getUnknownDetail: method(function() {
return this;
}, {
response: RetVal("root#unknownDetail")
}),
});
let RootFront = protocol.FrontClass(RootActor, {
initialize(client) {
this.actorID = "root";
protocol.Front.prototype.initialize.call(this, client);
// Root owns itself.
this.manage(this);
},
form(v, ctx, detail) {
this.lastForm = v;
}
});
const run_test = Test(function*() {
DebuggerServer.createRootActor = (conn => {
return RootActor(conn);
});
DebuggerServer.init(() => true);
const connection = DebuggerServer.connectPipe();
const conn = new DebuggerClient(connection);
const client = Async(conn);
yield client.connect();
let rootFront = RootFront(conn);
// Trigger some methods that return forms.
let retval = yield rootFront.getDefault();
do_check_true(retval instanceof RootFront);
do_check_true(rootFront.lastForm.childActor instanceof ChildFront);
retval = yield rootFront.getDetail1();
do_check_true(retval instanceof RootFront);
do_check_true(rootFront.lastForm.detailItem instanceof ChildFront);
retval = yield rootFront.getDetail2();
do_check_true(retval instanceof RootFront);
do_check_true(typeof(rootFront.lastForm) === "string");
// getUnknownDetail should fail, since no typeName is specified.
try {
yield rootFront.getUnknownDetail();
do_check_true(false);
} catch(ex) {
}
yield client.close();
});

View File

@ -65,6 +65,7 @@ support-files =
[test_protocol_simple.js]
[test_protocol_longstring.js]
[test_protocol_children.js]
[test_protocol_formtype.js]
[test_breakpoint-01.js]
[test_register_actor.js]
skip-if = toolkit == "gonk"