author | Dave Camp <dcamp@mozilla.com> |
Sat, 11 Oct 2014 13:08:07 -0700 | |
changeset 233197 | 4cf592b066efc4a56977a181d6aa4d81f78d3155 |
parent 233146 | 2878bf6cbc1f03db66ca60d665cd4a8667ffdfd9 |
child 233198 | 44cb72be622dc5581a944eab309738232ab104c3 |
push id | 4187 |
push user | bhearsum@mozilla.com |
push date | Fri, 28 Nov 2014 15:29:12 +0000 |
treeherder | mozilla-beta@f23cc6a30c11 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jryans, jsantell |
bugs | 1072080 |
milestone | 35.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/toolkit/devtools/server/actors/string.js +++ b/toolkit/devtools/server/actors/string.js @@ -74,32 +74,29 @@ exports.ShortLongString = Class({ release: function() { this.str = null; return promise.resolve(undefined); } }) 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() { this.initial = null; this.length = null; this.strPromise = null; protocol.Front.prototype.destroy.call(this); }, form: function(form) { - this.actorID = form.actorID; + this.actorID = form.actor; this.initial = form.initial; this.length = form.length; }, string: function() { if (!this.strPromise) { let promiseRest = (thusFar) => { if (thusFar.length === this.length)
--- a/toolkit/devtools/server/actors/webaudio.js +++ b/toolkit/devtools/server/actors/webaudio.js @@ -293,17 +293,21 @@ let AudioNodeActor = exports.AudioNodeAc }); /** * The corresponding Front object for the AudioNodeActor. */ 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); + } } }); /** * The Web Audio Actor handles simple interaction with an AudioContext * high-level methods. After instantiating this actor, you'll need to set it * up by calling setup(). */ @@ -430,16 +434,18 @@ let WebAudioActor = exports.WebAudioActo * to hibernation. This method is called automatically just before the * actor is destroyed. */ finalize: method(function() { if (!this._initialized) { 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; this._nativeToActorID = null; this._callWatcher.eraseRecording(); this._callWatcher.finalize(); this._callWatcher = null; }, {
--- a/toolkit/devtools/server/protocol.js +++ b/toolkit/devtools/server/protocol.js @@ -140,16 +140,17 @@ function identityWrite(v) { * @returns a type object that can be used in protocol definitions. */ types.addType = function(name, typeObject={}, options={}) { if (registeredTypes.has(name)) { throw Error("Type '" + name + "' already exists."); } let type = object.merge({ + toString() { return "[protocol type:" + name + "]"}, name: name, primitive: !(typeObject.read || typeObject.write), read: identityWrite, write: identityWrite }, typeObject); registeredTypes.set(name, type); @@ -253,38 +254,56 @@ types.addActorType = function(name) { return ctx.conn.getActor(v); } // Reading a response on the client side, check for an // existing front on the connection, and create the front // 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) => { // If returning a response from the server side, make sure // the actor is added to a parent object and return its form. if (v instanceof Actor) { 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. thawed: true }); return type; } @@ -819,16 +838,18 @@ let Actor = Class({ let sendEvent = this._sendEvent.bind(this, name) this.on(name, (...args) => { sendEvent.apply(null, args); }); } } }, + 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. return; } let request = this._actorSpec.events.get(name); let packet; try { @@ -903,23 +924,34 @@ let actorProto = function(actorProto) { if (actorProto._actorSpec) { throw new Error("actorProto called twice on the same actor prototype!"); } let protoSpec = { 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 = {}; spec.name = frozenSpec.name || name; spec.request = Request(object.merge({type: spec.name}, frozenSpec.request || undefined)); spec.response = Response(frozenSpec.response || undefined); spec.telemetry = frozenSpec.telemetry; spec.release = frozenSpec.release; @@ -1039,18 +1071,24 @@ let Front = Class({ * conn can be null if the subclass provides a conn property. * @param optional form * The json form provided by the server. * @constructor */ 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); } }, destroy: function() { // Reject all outstanding requests, they won't make sense after // the front is destroyed. while (this._requests && this._requests.length > 0) { @@ -1115,16 +1153,17 @@ let Front = Class({ let type = packet.type || undefined; if (this._clientSpec.events && this._clientSpec.events.has(type)) { let event = this._clientSpec.events.get(packet.type); let args; try { args = event.request.read(packet, this); } catch(ex) { console.error("Error reading event: " + packet.type); + console.exception(ex); throw ex; } if (event.pre) { event.pre.forEach((pre) => pre.apply(this, args)); } events.emit.apply(null, [this, event.name].concat(args)); return; }
--- a/toolkit/devtools/server/tests/unit/test_protocol_children.js +++ b/toolkit/devtools/server/tests/unit/test_protocol_children.js @@ -229,17 +229,19 @@ let RootFront = protocol.FrontClass(Root 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 = 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); },{ impl: "_getTemporaryChild" }), clearTemporaryChildren: protocol.custom(function() { if (!this._temporaryHolder) {
new file mode 100644 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_protocol_formtype.js @@ -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(); +});
--- a/toolkit/devtools/server/tests/unit/xpcshell.ini +++ b/toolkit/devtools/server/tests/unit/xpcshell.ini @@ -60,16 +60,17 @@ support-files = [test_eval-02.js] [test_eval-03.js] [test_eval-04.js] [test_eval-05.js] [test_protocol_async.js] [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" reason = bug 820380 [test_breakpoint-02.js] skip-if = toolkit == "gonk" reason = bug 820380 [test_breakpoint-03.js]