Bug 983331 - The debugger server protocol should describe itself. r=gozala
authorDave Camp <dcamp@mozilla.com>
Thu, 17 Apr 2014 16:25:07 -0700
changeset 197589 98d6d4c38fcc3115d56fc7f4f05452d1e077b72e
parent 197588 87dd13ef285c9b7ecb608d4b7fc34cff097d212a
child 197590 ec728bfdbb79cb5ef6a847d042bda0cbad1207d0
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgozala
bugs983331
milestone31.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
Bug 983331 - The debugger server protocol should describe itself. r=gozala
toolkit/devtools/server/actors/root.js
toolkit/devtools/server/main.js
toolkit/devtools/server/protocol.js
--- a/toolkit/devtools/server/actors/root.js
+++ b/toolkit/devtools/server/actors/root.js
@@ -354,16 +354,20 @@ RootActor.prototype = {
   onEcho: function (aRequest) {
     /*
      * Request packets are frozen. Copy aRequest, so that
      * DebuggerServerConnection.onPacket can attach a 'from' property.
      */
     return JSON.parse(JSON.stringify(aRequest));
   },
 
+  onProtocolDescription: function (aRequest) {
+    return protocol.dumpProtocolSpec()
+  },
+
   /* Support for DebuggerServer.addGlobalActor. */
   _createExtraActors: CommonCreateExtraActors,
   _appendExtraActors: CommonAppendExtraActors,
 
   /* ThreadActor hooks. */
 
   /**
    * Prepare to enter a nested event loop by disabling debuggee events.
@@ -394,10 +398,11 @@ RootActor.prototype = {
       windowUtils.suppressEventHandling(false);
     }
   }
 };
 
 RootActor.prototype.requestTypes = {
   "listTabs": RootActor.prototype.onListTabs,
   "listAddons": RootActor.prototype.onListAddons,
-  "echo": RootActor.prototype.onEcho
+  "echo": RootActor.prototype.onEcho,
+  "protocolDescription": RootActor.prototype.onProtocolDescription
 };
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -218,16 +218,18 @@ var DebuggerServer = {
 
     this.xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
     this.initTransport(aAllowConnectionCallback);
     this.addActors("resource://gre/modules/devtools/server/actors/root.js");
 
     this._initialized = true;
   },
 
+  protocol: require("devtools/server/protocol"),
+
   /**
    * Initialize the debugger server's transport variables.  This can be
    * in place of init() for cases where the jsdebugger isn't needed.
    *
    * @param function aAllowConnectionCallback
    *        The embedder-provider callback, that decides whether an incoming
    *        remote protocol conection should be allowed or refused.
    */
--- a/toolkit/devtools/server/protocol.js
+++ b/toolkit/devtools/server/protocol.js
@@ -173,16 +173,17 @@ types.addArrayType = function(subtype) {
 
   let name = "array:" + subtype.name;
 
   // Arrays of primitive types are primitive types themselves.
   if (subtype.primitive) {
     return types.addType(name);
   }
   return types.addType(name, {
+    category: "array",
     read: (v, ctx) => [subtype.read(i, ctx) for (i of v)],
     write: (v, ctx) => [subtype.write(i, ctx) for (i of v)]
   });
 };
 
 /**
  * Add a dict type to the type system.  This allows you to serialize
  * a JS object that contains non-primitive subtypes.
@@ -190,16 +191,18 @@ types.addArrayType = function(subtype) {
  * Properties of the value that aren't included in the specializations
  * will be serialized as primitive values.
  *
  * @param object specializations
  *    A dict of property names => type
  */
 types.addDictType = function(name, specializations) {
   return types.addType(name, {
+    category: "dict",
+    specializations: specializations,
     read: (v, ctx) => {
       let ret = {};
       for (let prop in v) {
         if (prop in specializations) {
           ret[prop] = types.getType(specializations[prop]).read(v[prop], ctx);
         } else {
           ret[prop] = v[prop];
         }
@@ -236,16 +239,17 @@ types.addDictType = function(name, speci
  * called during actor/front construction.
  *
  * @param string name
  *    The typestring to register.
  */
 types.addActorType = function(name) {
   let type = types.addType(name, {
     _actor: true,
+    category: "actor",
     read: (v, ctx, detail) => {
       // If we're reading a request on the server side, just
       // find the actor registered with this actorID.
       if (ctx instanceof Actor) {
         return ctx.conn.getActor(v);
       }
 
       // Reading a response on the client side, check for an
@@ -281,16 +285,17 @@ types.addActorType = function(name) {
     thawed: true
   });
   return type;
 }
 
 types.addNullableType = function(subtype) {
   subtype = types.getType(subtype);
   return types.addType("nullable:" + subtype.name, {
+    category: "nullable",
     read: (value, ctx) => {
       if (value == null) {
         return value;
       }
       return subtype.read(value, ctx);
     },
     write: (value, ctx) => {
       if (value == null) {
@@ -317,16 +322,17 @@ types.addNullableType = function(subtype
  */
 types.addActorDetail = function(name, actorType, detail) {
   actorType = types.getType(actorType);
   if (!actorType._actor) {
     throw Error("Details only apply to actor types, tried to add detail '" + detail + "'' to " + actorType.name + "\n");
   }
   return types.addType(name, {
     _actor: true,
+    category: "detail",
     read: (v, ctx) => actorType.read(v, ctx, detail),
     write: (v, ctx) => actorType.write(v, ctx, detail)
   });
 }
 
 /**
  * Register an actor lifetime.  This lets the type system find a parent
  * actor that differs from the actor fulfilling the request.
@@ -357,16 +363,17 @@ types.addLifetime = function(name, prop)
  */
 types.addLifetimeType = function(lifetime, subtype) {
   subtype = types.getType(subtype);
   if (!subtype._actor) {
     throw Error("Lifetimes only apply to actor types, tried to apply lifetime '" + lifetime + "'' to " + subtype.name);
   }
   let prop = registeredLifetimes.get(lifetime);
   return types.addType(lifetime + ":" + subtype.name, {
+    category: "lifetime",
     read: (value, ctx) => subtype.read(value, ctx[prop]),
     write: (value, ctx) => subtype.write(value, ctx[prop])
   })
 }
 
 // Add a few named primitive types.
 types.Primitive = types.addType("primitive");
 types.String = types.addType("string");
@@ -402,16 +409,23 @@ let Arg = Class({
   },
 
   write: function(arg, ctx) {
     return this.type.write(arg, ctx);
   },
 
   read: function(v, ctx, outArgs) {
     outArgs[this.index] = this.type.read(v, ctx);
+  },
+
+  describe: function() {
+    return {
+      _arg: this.index,
+      type: this.type.name,
+    }
   }
 });
 exports.Arg = Arg;
 
 /**
  * Placeholder for an options argument value that should be hoisted
  * into the packet.
  *
@@ -447,16 +461,23 @@ let Option = Class({
   read: function(v, ctx, outArgs, name) {
     if (outArgs[this.index] === undefined) {
       outArgs[this.index] = {};
     }
     if (v === undefined) {
       return;
     }
     outArgs[this.index][name] = this.type.read(v, ctx);
+  },
+
+  describe: function() {
+    return {
+      _option: this.index,
+      type: this.type.name,
+    }
   }
 });
 
 exports.Option = Option;
 
 /**
  * Placeholder for return values in a response template.
  *
@@ -469,16 +490,22 @@ let RetVal = Class({
   },
 
   write: function(v, ctx) {
     return this.type.write(v, ctx);
   },
 
   read: function(v, ctx) {
     return this.type.read(v, ctx);
+  },
+
+  describe: function() {
+    return {
+      _retval: this.type.name
+    }
   }
 });
 
 exports.RetVal = RetVal;
 
 /* Template handling functions */
 
 /**
@@ -513,16 +540,25 @@ function findPlaceholders(template, cons
     findPlaceholders(template[name], constructor, path, placeholders);
     path.pop();
   }
 
   return placeholders;
 }
 
 
+function describeTemplate(template) {
+  return JSON.parse(JSON.stringify(template, (key, value) => {
+    if (value.describe) {
+      return value.describe();
+    }
+    return value;
+  }));
+}
+
 /**
  * Manages a request template.
  *
  * @param object template
  *    The request template.
  * @construcor
  */
 let Request = Class({
@@ -565,16 +601,18 @@ let Request = Class({
     for (let templateArg of this.args) {
       let arg = templateArg.placeholder;
       let path = templateArg.path;
       let name = path[path.length - 1];
       arg.read(getPath(packet, path), ctx, fnArgs, name);
     }
     return fnArgs;
   },
+
+  describe: function() { return describeTemplate(this.template); }
 });
 
 /**
  * Manages a response template.
  *
  * @param object template
  *    The response template.
  * @construcor
@@ -619,17 +657,19 @@ let Response = Class({
    *    The object reading the response.
    */
   read: function(packet, ctx) {
     if (!this.retVal) {
       return undefined;
     }
     let v = getPath(packet, this.path);
     return this.retVal.read(v, ctx);
-  }
+  },
+
+  describe: function() { return describeTemplate(this.template); }
 });
 
 /**
  * Actor and Front implementations
  */
 
 /**
  * A protocol object that can manage the lifetime of other protocol
@@ -953,17 +993,20 @@ let actorProto = function(actorProto) {
 exports.ActorClass = function(proto) {
   if (!proto.typeName) {
     throw Error("Actor prototype must have a typeName member.");
   }
   proto.extends = Actor;
   if (!registeredTypes.has(proto.typeName)) {
     types.addActorType(proto.typeName);
   }
-  return Class(actorProto(proto));
+  let cls = Class(actorProto(proto));
+
+  registeredTypes.get(proto.typeName).actorSpec = proto._actorSpec;
+  return cls;
 };
 
 /**
  * Base class for client-side actor fronts.
  */
 let Front = Class({
   extends: Pool,
 
@@ -1234,8 +1277,62 @@ let frontProto = function(proto) {
  */
 exports.FrontClass = function(actorType, proto) {
   proto.actorType = actorType;
   proto.extends = Front;
   let cls = Class(frontProto(proto));
   registeredTypes.get(cls.prototype.typeName).frontClass = cls;
   return cls;
 }
+
+
+exports.dumpActorSpec = function(type) {
+  let actorSpec = type.actorSpec;
+  let ret = {
+    category: "actor",
+    typeName: type.name,
+    methods: [],
+    events: {}
+  };
+
+  for (let method of actorSpec.methods) {
+    ret.methods.push({
+      name: method.name,
+      release: method.release || undefined,
+      oneway: method.oneway || undefined,
+      request: method.request.describe(),
+      response: method.response.describe()
+    });
+  }
+
+  if (actorSpec.events) {
+    for (let [name, request] of actorSpec.events) {
+      ret.events[name] = request.describe();
+    }
+  }
+
+
+  JSON.stringify(ret);
+
+  return ret;
+}
+
+exports.dumpProtocolSpec = function() {
+  let ret = {
+    types: {},
+  };
+
+  for (let [name, type] of registeredTypes) {
+    // Force lazy instantiation if needed.
+    type = types.getType(name);
+    if (type.category === "dict") {
+      ret.types[name] = {
+        category: "dict",
+        typeName: name,
+        specializations: type.specializations
+      }
+    } else if (type.category === "actor") {
+      ret.types[name] = exports.dumpActorSpec(type);
+    }
+  }
+
+  return ret;
+}