toolkit/devtools/server/protocol.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/devtools/server/protocol.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1338 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +let {Cu} = require("chrome");
    1.11 +let Services = require("Services");
    1.12 +let promise = require("devtools/toolkit/deprecated-sync-thenables");
    1.13 +let {Class} = require("sdk/core/heritage");
    1.14 +let {EventTarget} = require("sdk/event/target");
    1.15 +let events = require("sdk/event/core");
    1.16 +let object = require("sdk/util/object");
    1.17 +
    1.18 +// Waiting for promise.done() to be added, see bug 851321
    1.19 +function promiseDone(err) {
    1.20 +  console.error(err);
    1.21 +  return promise.reject(err);
    1.22 +}
    1.23 +
    1.24 +/**
    1.25 + * Types: named marshallers/demarshallers.
    1.26 + *
    1.27 + * Types provide a 'write' function that takes a js representation and
    1.28 + * returns a protocol representation, and a "read" function that
    1.29 + * takes a protocol representation and returns a js representation.
    1.30 + *
    1.31 + * The read and write methods are also passed a context object that
    1.32 + * represent the actor or front requesting the translation.
    1.33 + *
    1.34 + * Types are referred to with a typestring.  Basic types are
    1.35 + * registered by name using addType, and more complex types can
    1.36 + * be generated by adding detail to the type name.
    1.37 + */
    1.38 +
    1.39 +let types = Object.create(null);
    1.40 +exports.types = types;
    1.41 +
    1.42 +let registeredTypes = new Map();
    1.43 +let registeredLifetimes = new Map();
    1.44 +
    1.45 +/**
    1.46 + * Return the type object associated with a given typestring.
    1.47 + * If passed a type object, it will be returned unchanged.
    1.48 + *
    1.49 + * Types can be registered with addType, or can be created on
    1.50 + * the fly with typestrings.  Examples:
    1.51 + *
    1.52 + *   boolean
    1.53 + *   threadActor
    1.54 + *   threadActor#detail
    1.55 + *   array:threadActor
    1.56 + *   array:array:threadActor#detail
    1.57 + *
    1.58 + * @param [typestring|type] type
    1.59 + *    Either a typestring naming a type or a type object.
    1.60 + *
    1.61 + * @returns a type object.
    1.62 + */
    1.63 +types.getType = function(type) {
    1.64 +  if (!type) {
    1.65 +    return types.Primitive;
    1.66 +  }
    1.67 +
    1.68 +  if (typeof(type) !== "string") {
    1.69 +    return type;
    1.70 +  }
    1.71 +
    1.72 +  // If already registered, we're done here.
    1.73 +  let reg = registeredTypes.get(type);
    1.74 +  if (reg) return reg;
    1.75 +
    1.76 +  // New type, see if it's a collection/lifetime type:
    1.77 +  let sep = type.indexOf(":");
    1.78 +  if (sep >= 0) {
    1.79 +    let collection = type.substring(0, sep);
    1.80 +    let subtype = types.getType(type.substring(sep + 1));
    1.81 +
    1.82 +    if (collection === "array") {
    1.83 +      return types.addArrayType(subtype);
    1.84 +    } else if (collection === "nullable") {
    1.85 +      return types.addNullableType(subtype);
    1.86 +    }
    1.87 +
    1.88 +    if (registeredLifetimes.has(collection)) {
    1.89 +      return types.addLifetimeType(collection, subtype);
    1.90 +    }
    1.91 +
    1.92 +    throw Error("Unknown collection type: " + collection);
    1.93 +  }
    1.94 +
    1.95 +  // Not a collection, might be actor detail
    1.96 +  let pieces = type.split("#", 2);
    1.97 +  if (pieces.length > 1) {
    1.98 +    return types.addActorDetail(type, pieces[0], pieces[1]);
    1.99 +  }
   1.100 +
   1.101 +  // Might be a lazily-loaded type
   1.102 +  if (type === "longstring") {
   1.103 +    require("devtools/server/actors/string");
   1.104 +    return registeredTypes.get("longstring");
   1.105 +  }
   1.106 +
   1.107 +  throw Error("Unknown type: " + type);
   1.108 +}
   1.109 +
   1.110 +/**
   1.111 + * Don't allow undefined when writing primitive types to packets.  If
   1.112 + * you want to allow undefined, use a nullable type.
   1.113 + */
   1.114 +function identityWrite(v) {
   1.115 +  if (v === undefined) {
   1.116 +    throw Error("undefined passed where a value is required");
   1.117 +  }
   1.118 +  return v;
   1.119 +}
   1.120 +
   1.121 +/**
   1.122 + * Add a type to the type system.
   1.123 + *
   1.124 + * When registering a type, you can provide `read` and `write` methods.
   1.125 + *
   1.126 + * The `read` method will be passed a JS object value from the JSON
   1.127 + * packet and must return a native representation.  The `write` method will
   1.128 + * be passed a native representation and should provide a JSONable value.
   1.129 + *
   1.130 + * These methods will both be passed a context.  The context is the object
   1.131 + * performing or servicing the request - on the server side it will be
   1.132 + * an Actor, on the client side it will be a Front.
   1.133 + *
   1.134 + * @param typestring name
   1.135 + *    Name to register
   1.136 + * @param object typeObject
   1.137 + *    An object whose properties will be stored in the type, including
   1.138 + *    the `read` and `write` methods.
   1.139 + * @param object options
   1.140 + *    Can specify `thawed` to prevent the type from being frozen.
   1.141 + *
   1.142 + * @returns a type object that can be used in protocol definitions.
   1.143 + */
   1.144 +types.addType = function(name, typeObject={}, options={}) {
   1.145 +  if (registeredTypes.has(name)) {
   1.146 +    throw Error("Type '" + name + "' already exists.");
   1.147 +  }
   1.148 +
   1.149 +  let type = object.merge({
   1.150 +    name: name,
   1.151 +    primitive: !(typeObject.read || typeObject.write),
   1.152 +    read: identityWrite,
   1.153 +    write: identityWrite
   1.154 +  }, typeObject);
   1.155 +
   1.156 +  registeredTypes.set(name, type);
   1.157 +
   1.158 +  if (!options.thawed) {
   1.159 +    Object.freeze(type);
   1.160 +  }
   1.161 +
   1.162 +  return type;
   1.163 +};
   1.164 +
   1.165 +/**
   1.166 + * Add an array type to the type system.
   1.167 + *
   1.168 + * getType() will call this function if provided an "array:<type>"
   1.169 + * typestring.
   1.170 + *
   1.171 + * @param type subtype
   1.172 + *    The subtype to be held by the array.
   1.173 + */
   1.174 +types.addArrayType = function(subtype) {
   1.175 +  subtype = types.getType(subtype);
   1.176 +
   1.177 +  let name = "array:" + subtype.name;
   1.178 +
   1.179 +  // Arrays of primitive types are primitive types themselves.
   1.180 +  if (subtype.primitive) {
   1.181 +    return types.addType(name);
   1.182 +  }
   1.183 +  return types.addType(name, {
   1.184 +    category: "array",
   1.185 +    read: (v, ctx) => [subtype.read(i, ctx) for (i of v)],
   1.186 +    write: (v, ctx) => [subtype.write(i, ctx) for (i of v)]
   1.187 +  });
   1.188 +};
   1.189 +
   1.190 +/**
   1.191 + * Add a dict type to the type system.  This allows you to serialize
   1.192 + * a JS object that contains non-primitive subtypes.
   1.193 + *
   1.194 + * Properties of the value that aren't included in the specializations
   1.195 + * will be serialized as primitive values.
   1.196 + *
   1.197 + * @param object specializations
   1.198 + *    A dict of property names => type
   1.199 + */
   1.200 +types.addDictType = function(name, specializations) {
   1.201 +  return types.addType(name, {
   1.202 +    category: "dict",
   1.203 +    specializations: specializations,
   1.204 +    read: (v, ctx) => {
   1.205 +      let ret = {};
   1.206 +      for (let prop in v) {
   1.207 +        if (prop in specializations) {
   1.208 +          ret[prop] = types.getType(specializations[prop]).read(v[prop], ctx);
   1.209 +        } else {
   1.210 +          ret[prop] = v[prop];
   1.211 +        }
   1.212 +      }
   1.213 +      return ret;
   1.214 +    },
   1.215 +
   1.216 +    write: (v, ctx) => {
   1.217 +      let ret = {};
   1.218 +      for (let prop in v) {
   1.219 +        if (prop in specializations) {
   1.220 +          ret[prop] = types.getType(specializations[prop]).write(v[prop], ctx);
   1.221 +        } else {
   1.222 +          ret[prop] = v[prop];
   1.223 +        }
   1.224 +      }
   1.225 +      return ret;
   1.226 +    }
   1.227 +  })
   1.228 +}
   1.229 +
   1.230 +/**
   1.231 + * Register an actor type with the type system.
   1.232 + *
   1.233 + * Types are marshalled differently when communicating server->client
   1.234 + * than they are when communicating client->server.  The server needs
   1.235 + * to provide useful information to the client, so uses the actor's
   1.236 + * `form` method to get a json representation of the actor.  When
   1.237 + * making a request from the client we only need the actor ID string.
   1.238 + *
   1.239 + * This function can be called before the associated actor has been
   1.240 + * constructed, but the read and write methods won't work until
   1.241 + * the associated addActorImpl or addActorFront methods have been
   1.242 + * called during actor/front construction.
   1.243 + *
   1.244 + * @param string name
   1.245 + *    The typestring to register.
   1.246 + */
   1.247 +types.addActorType = function(name) {
   1.248 +  let type = types.addType(name, {
   1.249 +    _actor: true,
   1.250 +    category: "actor",
   1.251 +    read: (v, ctx, detail) => {
   1.252 +      // If we're reading a request on the server side, just
   1.253 +      // find the actor registered with this actorID.
   1.254 +      if (ctx instanceof Actor) {
   1.255 +        return ctx.conn.getActor(v);
   1.256 +      }
   1.257 +
   1.258 +      // Reading a response on the client side, check for an
   1.259 +      // existing front on the connection, and create the front
   1.260 +      // if it isn't found.
   1.261 +      let actorID = typeof(v) === "string" ? v : v.actor;
   1.262 +      let front = ctx.conn.getActor(actorID);
   1.263 +      if (front) {
   1.264 +        front.form(v, detail, ctx);
   1.265 +      } else {
   1.266 +        front = new type.frontClass(ctx.conn, v, detail, ctx)
   1.267 +        front.actorID = actorID;
   1.268 +        ctx.marshallPool().manage(front);
   1.269 +      }
   1.270 +      return front;
   1.271 +    },
   1.272 +    write: (v, ctx, detail) => {
   1.273 +      // If returning a response from the server side, make sure
   1.274 +      // the actor is added to a parent object and return its form.
   1.275 +      if (v instanceof Actor) {
   1.276 +        if (!v.actorID) {
   1.277 +          ctx.marshallPool().manage(v);
   1.278 +        }
   1.279 +        return v.form(detail);
   1.280 +      }
   1.281 +
   1.282 +      // Writing a request from the client side, just send the actor id.
   1.283 +      return v.actorID;
   1.284 +    },
   1.285 +  }, {
   1.286 +    // We usually freeze types, but actor types are updated when clients are
   1.287 +    // created, so don't freeze yet.
   1.288 +    thawed: true
   1.289 +  });
   1.290 +  return type;
   1.291 +}
   1.292 +
   1.293 +types.addNullableType = function(subtype) {
   1.294 +  subtype = types.getType(subtype);
   1.295 +  return types.addType("nullable:" + subtype.name, {
   1.296 +    category: "nullable",
   1.297 +    read: (value, ctx) => {
   1.298 +      if (value == null) {
   1.299 +        return value;
   1.300 +      }
   1.301 +      return subtype.read(value, ctx);
   1.302 +    },
   1.303 +    write: (value, ctx) => {
   1.304 +      if (value == null) {
   1.305 +        return value;
   1.306 +      }
   1.307 +      return subtype.write(value, ctx);
   1.308 +    }
   1.309 +  });
   1.310 +}
   1.311 +
   1.312 +/**
   1.313 + * Register an actor detail type.  This is just like an actor type, but
   1.314 + * will pass a detail hint to the actor's form method during serialization/
   1.315 + * deserialization.
   1.316 + *
   1.317 + * This is called by getType() when passed an 'actorType#detail' string.
   1.318 + *
   1.319 + * @param string name
   1.320 + *   The typestring to register this type as.
   1.321 + * @param type actorType
   1.322 + *   The actor type you'll be detailing.
   1.323 + * @param string detail
   1.324 + *   The detail to pass.
   1.325 + */
   1.326 +types.addActorDetail = function(name, actorType, detail) {
   1.327 +  actorType = types.getType(actorType);
   1.328 +  if (!actorType._actor) {
   1.329 +    throw Error("Details only apply to actor types, tried to add detail '" + detail + "'' to " + actorType.name + "\n");
   1.330 +  }
   1.331 +  return types.addType(name, {
   1.332 +    _actor: true,
   1.333 +    category: "detail",
   1.334 +    read: (v, ctx) => actorType.read(v, ctx, detail),
   1.335 +    write: (v, ctx) => actorType.write(v, ctx, detail)
   1.336 +  });
   1.337 +}
   1.338 +
   1.339 +/**
   1.340 + * Register an actor lifetime.  This lets the type system find a parent
   1.341 + * actor that differs from the actor fulfilling the request.
   1.342 + *
   1.343 + * @param string name
   1.344 + *    The lifetime name to use in typestrings.
   1.345 + * @param string prop
   1.346 + *    The property of the actor that holds the parent that should be used.
   1.347 + */
   1.348 +types.addLifetime = function(name, prop) {
   1.349 +  if (registeredLifetimes.has(name)) {
   1.350 +    throw Error("Lifetime '" + name + "' already registered.");
   1.351 +  }
   1.352 +  registeredLifetimes.set(name, prop);
   1.353 +}
   1.354 +
   1.355 +/**
   1.356 + * Register a lifetime type.  This creates an actor type tied to the given
   1.357 + * lifetime.
   1.358 + *
   1.359 + * This is called by getType() when passed a '<lifetimeType>:<actorType>'
   1.360 + * typestring.
   1.361 + *
   1.362 + * @param string lifetime
   1.363 + *    A lifetime string previously regisered with addLifetime()
   1.364 + * @param type subtype
   1.365 + *    An actor type
   1.366 + */
   1.367 +types.addLifetimeType = function(lifetime, subtype) {
   1.368 +  subtype = types.getType(subtype);
   1.369 +  if (!subtype._actor) {
   1.370 +    throw Error("Lifetimes only apply to actor types, tried to apply lifetime '" + lifetime + "'' to " + subtype.name);
   1.371 +  }
   1.372 +  let prop = registeredLifetimes.get(lifetime);
   1.373 +  return types.addType(lifetime + ":" + subtype.name, {
   1.374 +    category: "lifetime",
   1.375 +    read: (value, ctx) => subtype.read(value, ctx[prop]),
   1.376 +    write: (value, ctx) => subtype.write(value, ctx[prop])
   1.377 +  })
   1.378 +}
   1.379 +
   1.380 +// Add a few named primitive types.
   1.381 +types.Primitive = types.addType("primitive");
   1.382 +types.String = types.addType("string");
   1.383 +types.Number = types.addType("number");
   1.384 +types.Boolean = types.addType("boolean");
   1.385 +types.JSON = types.addType("json");
   1.386 +
   1.387 +/**
   1.388 + * Request/Response templates and generation
   1.389 + *
   1.390 + * Request packets are specified as json templates with
   1.391 + * Arg and Option placeholders where arguments should be
   1.392 + * placed.
   1.393 + *
   1.394 + * Reponse packets are also specified as json templates,
   1.395 + * with a RetVal placeholder where the return value should be
   1.396 + * placed.
   1.397 + */
   1.398 +
   1.399 +/**
   1.400 + * Placeholder for simple arguments.
   1.401 + *
   1.402 + * @param number index
   1.403 + *    The argument index to place at this position.
   1.404 + * @param type type
   1.405 + *    The argument should be marshalled as this type.
   1.406 + * @constructor
   1.407 + */
   1.408 +let Arg = Class({
   1.409 +  initialize: function(index, type) {
   1.410 +    this.index = index;
   1.411 +    this.type = types.getType(type);
   1.412 +  },
   1.413 +
   1.414 +  write: function(arg, ctx) {
   1.415 +    return this.type.write(arg, ctx);
   1.416 +  },
   1.417 +
   1.418 +  read: function(v, ctx, outArgs) {
   1.419 +    outArgs[this.index] = this.type.read(v, ctx);
   1.420 +  },
   1.421 +
   1.422 +  describe: function() {
   1.423 +    return {
   1.424 +      _arg: this.index,
   1.425 +      type: this.type.name,
   1.426 +    }
   1.427 +  }
   1.428 +});
   1.429 +exports.Arg = Arg;
   1.430 +
   1.431 +/**
   1.432 + * Placeholder for an options argument value that should be hoisted
   1.433 + * into the packet.
   1.434 + *
   1.435 + * If provided in a method specification:
   1.436 + *
   1.437 + *   { optionArg: Option(1)}
   1.438 + *
   1.439 + * Then arguments[1].optionArg will be placed in the packet in this
   1.440 + * value's place.
   1.441 + *
   1.442 + * @param number index
   1.443 + *    The argument index of the options value.
   1.444 + * @param type type
   1.445 + *    The argument should be marshalled as this type.
   1.446 + * @constructor
   1.447 + */
   1.448 +let Option = Class({
   1.449 +  extends: Arg,
   1.450 +  initialize: function(index, type) {
   1.451 +    Arg.prototype.initialize.call(this, index, type)
   1.452 +  },
   1.453 +
   1.454 +  write: function(arg, ctx, name) {
   1.455 +    if (!arg) {
   1.456 +      return undefined;
   1.457 +    }
   1.458 +    let v = arg[name] || undefined;
   1.459 +    if (v === undefined) {
   1.460 +      return undefined;
   1.461 +    }
   1.462 +    return this.type.write(v, ctx);
   1.463 +  },
   1.464 +  read: function(v, ctx, outArgs, name) {
   1.465 +    if (outArgs[this.index] === undefined) {
   1.466 +      outArgs[this.index] = {};
   1.467 +    }
   1.468 +    if (v === undefined) {
   1.469 +      return;
   1.470 +    }
   1.471 +    outArgs[this.index][name] = this.type.read(v, ctx);
   1.472 +  },
   1.473 +
   1.474 +  describe: function() {
   1.475 +    return {
   1.476 +      _option: this.index,
   1.477 +      type: this.type.name,
   1.478 +    }
   1.479 +  }
   1.480 +});
   1.481 +
   1.482 +exports.Option = Option;
   1.483 +
   1.484 +/**
   1.485 + * Placeholder for return values in a response template.
   1.486 + *
   1.487 + * @param type type
   1.488 + *    The return value should be marshalled as this type.
   1.489 + */
   1.490 +let RetVal = Class({
   1.491 +  initialize: function(type) {
   1.492 +    this.type = types.getType(type);
   1.493 +  },
   1.494 +
   1.495 +  write: function(v, ctx) {
   1.496 +    return this.type.write(v, ctx);
   1.497 +  },
   1.498 +
   1.499 +  read: function(v, ctx) {
   1.500 +    return this.type.read(v, ctx);
   1.501 +  },
   1.502 +
   1.503 +  describe: function() {
   1.504 +    return {
   1.505 +      _retval: this.type.name
   1.506 +    }
   1.507 +  }
   1.508 +});
   1.509 +
   1.510 +exports.RetVal = RetVal;
   1.511 +
   1.512 +/* Template handling functions */
   1.513 +
   1.514 +/**
   1.515 + * Get the value at a given path, or undefined if not found.
   1.516 + */
   1.517 +function getPath(obj, path) {
   1.518 +  for (let name of path) {
   1.519 +    if (!(name in obj)) {
   1.520 +      return undefined;
   1.521 +    }
   1.522 +    obj = obj[name];
   1.523 +  }
   1.524 +  return obj;
   1.525 +}
   1.526 +
   1.527 +/**
   1.528 + * Find Placeholders in the template and save them along with their
   1.529 + * paths.
   1.530 + */
   1.531 +function findPlaceholders(template, constructor, path=[], placeholders=[]) {
   1.532 +  if (!template || typeof(template) != "object") {
   1.533 +    return placeholders;
   1.534 +  }
   1.535 +
   1.536 +  if (template instanceof constructor) {
   1.537 +    placeholders.push({ placeholder: template, path: [p for (p of path)] });
   1.538 +    return placeholders;
   1.539 +  }
   1.540 +
   1.541 +  for (let name in template) {
   1.542 +    path.push(name);
   1.543 +    findPlaceholders(template[name], constructor, path, placeholders);
   1.544 +    path.pop();
   1.545 +  }
   1.546 +
   1.547 +  return placeholders;
   1.548 +}
   1.549 +
   1.550 +
   1.551 +function describeTemplate(template) {
   1.552 +  return JSON.parse(JSON.stringify(template, (key, value) => {
   1.553 +    if (value.describe) {
   1.554 +      return value.describe();
   1.555 +    }
   1.556 +    return value;
   1.557 +  }));
   1.558 +}
   1.559 +
   1.560 +/**
   1.561 + * Manages a request template.
   1.562 + *
   1.563 + * @param object template
   1.564 + *    The request template.
   1.565 + * @construcor
   1.566 + */
   1.567 +let Request = Class({
   1.568 +  initialize: function(template={}) {
   1.569 +    this.type = template.type;
   1.570 +    this.template = template;
   1.571 +    this.args = findPlaceholders(template, Arg);
   1.572 +  },
   1.573 +
   1.574 +  /**
   1.575 +   * Write a request.
   1.576 +   *
   1.577 +   * @param array fnArgs
   1.578 +   *    The function arguments to place in the request.
   1.579 +   * @param object ctx
   1.580 +   *    The object making the request.
   1.581 +   * @returns a request packet.
   1.582 +   */
   1.583 +  write: function(fnArgs, ctx) {
   1.584 +    let str = JSON.stringify(this.template, (key, value) => {
   1.585 +      if (value instanceof Arg) {
   1.586 +        return value.write(fnArgs[value.index], ctx, key);
   1.587 +      }
   1.588 +      return value;
   1.589 +    });
   1.590 +    return JSON.parse(str);
   1.591 +  },
   1.592 +
   1.593 +  /**
   1.594 +   * Read a request.
   1.595 +   *
   1.596 +   * @param object packet
   1.597 +   *    The request packet.
   1.598 +   * @param object ctx
   1.599 +   *    The object making the request.
   1.600 +   * @returns an arguments array
   1.601 +   */
   1.602 +  read: function(packet, ctx) {
   1.603 +    let fnArgs = [];
   1.604 +    for (let templateArg of this.args) {
   1.605 +      let arg = templateArg.placeholder;
   1.606 +      let path = templateArg.path;
   1.607 +      let name = path[path.length - 1];
   1.608 +      arg.read(getPath(packet, path), ctx, fnArgs, name);
   1.609 +    }
   1.610 +    return fnArgs;
   1.611 +  },
   1.612 +
   1.613 +  describe: function() { return describeTemplate(this.template); }
   1.614 +});
   1.615 +
   1.616 +/**
   1.617 + * Manages a response template.
   1.618 + *
   1.619 + * @param object template
   1.620 + *    The response template.
   1.621 + * @construcor
   1.622 + */
   1.623 +let Response = Class({
   1.624 +  initialize: function(template={}) {
   1.625 +    this.template = template;
   1.626 +    let placeholders = findPlaceholders(template, RetVal);
   1.627 +    if (placeholders.length > 1) {
   1.628 +      throw Error("More than one RetVal specified in response");
   1.629 +    }
   1.630 +    let placeholder = placeholders.shift();
   1.631 +    if (placeholder) {
   1.632 +      this.retVal = placeholder.placeholder;
   1.633 +      this.path = placeholder.path;
   1.634 +    }
   1.635 +  },
   1.636 +
   1.637 +  /**
   1.638 +   * Write a response for the given return value.
   1.639 +   *
   1.640 +   * @param val ret
   1.641 +   *    The return value.
   1.642 +   * @param object ctx
   1.643 +   *    The object writing the response.
   1.644 +   */
   1.645 +  write: function(ret, ctx) {
   1.646 +    return JSON.parse(JSON.stringify(this.template, function(key, value) {
   1.647 +      if (value instanceof RetVal) {
   1.648 +        return value.write(ret, ctx);
   1.649 +      }
   1.650 +      return value;
   1.651 +    }));
   1.652 +  },
   1.653 +
   1.654 +  /**
   1.655 +   * Read a return value from the given response.
   1.656 +   *
   1.657 +   * @param object packet
   1.658 +   *    The response packet.
   1.659 +   * @param object ctx
   1.660 +   *    The object reading the response.
   1.661 +   */
   1.662 +  read: function(packet, ctx) {
   1.663 +    if (!this.retVal) {
   1.664 +      return undefined;
   1.665 +    }
   1.666 +    let v = getPath(packet, this.path);
   1.667 +    return this.retVal.read(v, ctx);
   1.668 +  },
   1.669 +
   1.670 +  describe: function() { return describeTemplate(this.template); }
   1.671 +});
   1.672 +
   1.673 +/**
   1.674 + * Actor and Front implementations
   1.675 + */
   1.676 +
   1.677 +/**
   1.678 + * A protocol object that can manage the lifetime of other protocol
   1.679 + * objects.
   1.680 + */
   1.681 +let Pool = Class({
   1.682 +  extends: EventTarget,
   1.683 +
   1.684 +  /**
   1.685 +   * Pools are used on both sides of the connection to help coordinate
   1.686 +   * lifetimes.
   1.687 +   *
   1.688 +   * @param optional conn
   1.689 +   *   Either a DebuggerServerConnection or a DebuggerClient.  Must have
   1.690 +   *   addActorPool, removeActorPool, and poolFor.
   1.691 +   *   conn can be null if the subclass provides a conn property.
   1.692 +   * @constructor
   1.693 +   */
   1.694 +  initialize: function(conn) {
   1.695 +    if (conn) {
   1.696 +      this.conn = conn;
   1.697 +    }
   1.698 +  },
   1.699 +
   1.700 +  /**
   1.701 +   * Return the parent pool for this client.
   1.702 +   */
   1.703 +  parent: function() { return this.conn.poolFor(this.actorID) },
   1.704 +
   1.705 +  /**
   1.706 +   * Override this if you want actors returned by this actor
   1.707 +   * to belong to a different actor by default.
   1.708 +   */
   1.709 +  marshallPool: function() { return this; },
   1.710 +
   1.711 +  /**
   1.712 +   * Pool is the base class for all actors, even leaf nodes.
   1.713 +   * If the child map is actually referenced, go ahead and create
   1.714 +   * the stuff needed by the pool.
   1.715 +   */
   1.716 +  __poolMap: null,
   1.717 +  get _poolMap() {
   1.718 +    if (this.__poolMap) return this.__poolMap;
   1.719 +    this.__poolMap = new Map();
   1.720 +    this.conn.addActorPool(this);
   1.721 +    return this.__poolMap;
   1.722 +  },
   1.723 +
   1.724 +  /**
   1.725 +   * Add an actor as a child of this pool.
   1.726 +   */
   1.727 +  manage: function(actor) {
   1.728 +    if (!actor.actorID) {
   1.729 +      actor.actorID = this.conn.allocID(actor.actorPrefix || actor.typeName);
   1.730 +    }
   1.731 +
   1.732 +    this._poolMap.set(actor.actorID, actor);
   1.733 +    return actor;
   1.734 +  },
   1.735 +
   1.736 +  /**
   1.737 +   * Remove an actor as a child of this pool.
   1.738 +   */
   1.739 +  unmanage: function(actor) {
   1.740 +    this.__poolMap.delete(actor.actorID);
   1.741 +  },
   1.742 +
   1.743 +  // true if the given actor ID exists in the pool.
   1.744 +  has: function(actorID) this.__poolMap && this._poolMap.has(actorID),
   1.745 +
   1.746 +  // The actor for a given actor id stored in this pool
   1.747 +  actor: function(actorID) this.__poolMap ? this._poolMap.get(actorID) : null,
   1.748 +
   1.749 +  // Same as actor, should update debugger connection to use 'actor'
   1.750 +  // and then remove this.
   1.751 +  get: function(actorID) this.__poolMap ? this._poolMap.get(actorID) : null,
   1.752 +
   1.753 +  // True if this pool has no children.
   1.754 +  isEmpty: function() !this.__poolMap || this._poolMap.size == 0,
   1.755 +
   1.756 +  /**
   1.757 +   * Destroy this item, removing it from a parent if it has one,
   1.758 +   * and destroying all children if necessary.
   1.759 +   */
   1.760 +  destroy: function() {
   1.761 +    let parent = this.parent();
   1.762 +    if (parent) {
   1.763 +      parent.unmanage(this);
   1.764 +    }
   1.765 +    if (!this.__poolMap) {
   1.766 +      return;
   1.767 +    }
   1.768 +    for (let actor of this.__poolMap.values()) {
   1.769 +      // Self-owned actors are ok, but don't need destroying twice.
   1.770 +      if (actor === this) {
   1.771 +        continue;
   1.772 +      }
   1.773 +      let destroy = actor.destroy;
   1.774 +      if (destroy) {
   1.775 +        // Disconnect destroy while we're destroying in case of (misbehaving)
   1.776 +        // circular ownership.
   1.777 +        actor.destroy = null;
   1.778 +        destroy.call(actor);
   1.779 +        actor.destroy = destroy;
   1.780 +      }
   1.781 +    };
   1.782 +    this.conn.removeActorPool(this, true);
   1.783 +    this.__poolMap.clear();
   1.784 +    this.__poolMap = null;
   1.785 +  },
   1.786 +
   1.787 +  /**
   1.788 +   * For getting along with the debugger server pools, should be removable
   1.789 +   * eventually.
   1.790 +   */
   1.791 +  cleanup: function() {
   1.792 +    this.destroy();
   1.793 +  }
   1.794 +});
   1.795 +exports.Pool = Pool;
   1.796 +
   1.797 +/**
   1.798 + * An actor in the actor tree.
   1.799 + */
   1.800 +let Actor = Class({
   1.801 +  extends: Pool,
   1.802 +
   1.803 +  // Will contain the actor's ID
   1.804 +  actorID: null,
   1.805 +
   1.806 +  /**
   1.807 +   * Initialize an actor.
   1.808 +   *
   1.809 +   * @param optional conn
   1.810 +   *   Either a DebuggerServerConnection or a DebuggerClient.  Must have
   1.811 +   *   addActorPool, removeActorPool, and poolFor.
   1.812 +   *   conn can be null if the subclass provides a conn property.
   1.813 +   * @constructor
   1.814 +   */
   1.815 +  initialize: function(conn) {
   1.816 +    Pool.prototype.initialize.call(this, conn);
   1.817 +
   1.818 +    // Forward events to the connection.
   1.819 +    if (this._actorSpec && this._actorSpec.events) {
   1.820 +      for (let key of this._actorSpec.events.keys()) {
   1.821 +        let name = key;
   1.822 +        let sendEvent = this._sendEvent.bind(this, name)
   1.823 +        this.on(name, (...args) => {
   1.824 +          sendEvent.apply(null, args);
   1.825 +        });
   1.826 +      }
   1.827 +    }
   1.828 +  },
   1.829 +
   1.830 +  _sendEvent: function(name, ...args) {
   1.831 +    if (!this._actorSpec.events.has(name)) {
   1.832 +      // It's ok to emit events that don't go over the wire.
   1.833 +      return;
   1.834 +    }
   1.835 +    let request = this._actorSpec.events.get(name);
   1.836 +    let packet = request.write(args, this);
   1.837 +    packet.from = packet.from || this.actorID;
   1.838 +    this.conn.send(packet);
   1.839 +  },
   1.840 +
   1.841 +  destroy: function() {
   1.842 +    Pool.prototype.destroy.call(this);
   1.843 +    this.actorID = null;
   1.844 +  },
   1.845 +
   1.846 +  /**
   1.847 +   * Override this method in subclasses to serialize the actor.
   1.848 +   * @param [optional] string hint
   1.849 +   *   Optional string to customize the form.
   1.850 +   * @returns A jsonable object.
   1.851 +   */
   1.852 +  form: function(hint) {
   1.853 +    return { actor: this.actorID }
   1.854 +  },
   1.855 +
   1.856 +  writeError: function(err) {
   1.857 +    console.error(err);
   1.858 +    if (err.stack) {
   1.859 +      dump(err.stack);
   1.860 +    }
   1.861 +    this.conn.send({
   1.862 +      from: this.actorID,
   1.863 +      error: "unknownError",
   1.864 +      message: err.toString()
   1.865 +    });
   1.866 +  },
   1.867 +
   1.868 +  _queueResponse: function(create) {
   1.869 +    let pending = this._pendingResponse || promise.resolve(null);
   1.870 +    let response = create(pending);
   1.871 +    this._pendingResponse = response;
   1.872 +  }
   1.873 +});
   1.874 +exports.Actor = Actor;
   1.875 +
   1.876 +/**
   1.877 + * Tags a prtotype method as an actor method implementation.
   1.878 + *
   1.879 + * @param function fn
   1.880 + *    The implementation function, will be returned.
   1.881 + * @param spec
   1.882 + *    The method specification, with the following (optional) properties:
   1.883 + *      request (object): a request template.
   1.884 + *      response (object): a response template.
   1.885 + *      oneway (bool): 'true' if no response should be sent.
   1.886 + *      telemetry (string): Telemetry probe ID for measuring completion time.
   1.887 + */
   1.888 +exports.method = function(fn, spec={}) {
   1.889 +  fn._methodSpec = Object.freeze(spec);
   1.890 +  if (spec.request) Object.freeze(spec.request);
   1.891 +  if (spec.response) Object.freeze(spec.response);
   1.892 +  return fn;
   1.893 +}
   1.894 +
   1.895 +/**
   1.896 + * Process an actor definition from its prototype and generate
   1.897 + * request handlers.
   1.898 + */
   1.899 +let actorProto = function(actorProto) {
   1.900 +  if (actorProto._actorSpec) {
   1.901 +    throw new Error("actorProto called twice on the same actor prototype!");
   1.902 +  }
   1.903 +
   1.904 +  let protoSpec = {
   1.905 +    methods: [],
   1.906 +  };
   1.907 +
   1.908 +  // Find method specifications attached to prototype properties.
   1.909 +  for (let name of Object.getOwnPropertyNames(actorProto)) {
   1.910 +    let desc = Object.getOwnPropertyDescriptor(actorProto, name);
   1.911 +    if (!desc.value) {
   1.912 +      continue;
   1.913 +    }
   1.914 +
   1.915 +    if (desc.value._methodSpec) {
   1.916 +      let frozenSpec = desc.value._methodSpec;
   1.917 +      let spec = {};
   1.918 +      spec.name = frozenSpec.name || name;
   1.919 +      spec.request = Request(object.merge({type: spec.name}, frozenSpec.request || undefined));
   1.920 +      spec.response = Response(frozenSpec.response || undefined);
   1.921 +      spec.telemetry = frozenSpec.telemetry;
   1.922 +      spec.release = frozenSpec.release;
   1.923 +      spec.oneway = frozenSpec.oneway;
   1.924 +
   1.925 +      protoSpec.methods.push(spec);
   1.926 +    }
   1.927 +  }
   1.928 +
   1.929 +  // Find event specifications
   1.930 +  if (actorProto.events) {
   1.931 +    protoSpec.events = new Map();
   1.932 +    for (let name in actorProto.events) {
   1.933 +      let eventRequest = actorProto.events[name];
   1.934 +      Object.freeze(eventRequest);
   1.935 +      protoSpec.events.set(name, Request(object.merge({type: name}, eventRequest)));
   1.936 +    }
   1.937 +  }
   1.938 +
   1.939 +  // Generate request handlers for each method definition
   1.940 +  actorProto.requestTypes = Object.create(null);
   1.941 +  protoSpec.methods.forEach(spec => {
   1.942 +    let handler = function(packet, conn) {
   1.943 +      try {
   1.944 +        let args = spec.request.read(packet, this);
   1.945 +
   1.946 +        let ret = this[spec.name].apply(this, args);
   1.947 +
   1.948 +        if (spec.oneway) {
   1.949 +          // No need to send a response.
   1.950 +          return;
   1.951 +        }
   1.952 +
   1.953 +        let sendReturn = (ret) => {
   1.954 +          let response = spec.response.write(ret, this);
   1.955 +          response.from = this.actorID;
   1.956 +          // If spec.release has been specified, destroy the object.
   1.957 +          if (spec.release) {
   1.958 +            try {
   1.959 +              this.destroy();
   1.960 +            } catch(e) {
   1.961 +              this.writeError(e);
   1.962 +              return;
   1.963 +            }
   1.964 +          }
   1.965 +
   1.966 +          conn.send(response);
   1.967 +        };
   1.968 +
   1.969 +        this._queueResponse(p => {
   1.970 +          return p
   1.971 +            .then(() => ret)
   1.972 +            .then(sendReturn)
   1.973 +            .then(null, this.writeError.bind(this));
   1.974 +        })
   1.975 +      } catch(e) {
   1.976 +        this._queueResponse(p => {
   1.977 +          return p.then(() => this.writeError(e));
   1.978 +        });
   1.979 +      }
   1.980 +    };
   1.981 +
   1.982 +    actorProto.requestTypes[spec.request.type] = handler;
   1.983 +  });
   1.984 +
   1.985 +  actorProto._actorSpec = protoSpec;
   1.986 +  return actorProto;
   1.987 +}
   1.988 +
   1.989 +/**
   1.990 + * Create an actor class for the given actor prototype.
   1.991 + *
   1.992 + * @param object proto
   1.993 + *    The object prototype.  Must have a 'typeName' property,
   1.994 + *    should have method definitions, can have event definitions.
   1.995 + */
   1.996 +exports.ActorClass = function(proto) {
   1.997 +  if (!proto.typeName) {
   1.998 +    throw Error("Actor prototype must have a typeName member.");
   1.999 +  }
  1.1000 +  proto.extends = Actor;
  1.1001 +  if (!registeredTypes.has(proto.typeName)) {
  1.1002 +    types.addActorType(proto.typeName);
  1.1003 +  }
  1.1004 +  let cls = Class(actorProto(proto));
  1.1005 +
  1.1006 +  registeredTypes.get(proto.typeName).actorSpec = proto._actorSpec;
  1.1007 +  return cls;
  1.1008 +};
  1.1009 +
  1.1010 +/**
  1.1011 + * Base class for client-side actor fronts.
  1.1012 + */
  1.1013 +let Front = Class({
  1.1014 +  extends: Pool,
  1.1015 +
  1.1016 +  actorID: null,
  1.1017 +
  1.1018 +  /**
  1.1019 +   * The base class for client-side actor fronts.
  1.1020 +   *
  1.1021 +   * @param optional conn
  1.1022 +   *   Either a DebuggerServerConnection or a DebuggerClient.  Must have
  1.1023 +   *   addActorPool, removeActorPool, and poolFor.
  1.1024 +   *   conn can be null if the subclass provides a conn property.
  1.1025 +   * @param optional form
  1.1026 +   *   The json form provided by the server.
  1.1027 +   * @constructor
  1.1028 +   */
  1.1029 +  initialize: function(conn=null, form=null, detail=null, context=null) {
  1.1030 +    Pool.prototype.initialize.call(this, conn);
  1.1031 +    this._requests = [];
  1.1032 +    if (form) {
  1.1033 +      this.actorID = form.actor;
  1.1034 +      this.form(form, detail, context);
  1.1035 +    }
  1.1036 +  },
  1.1037 +
  1.1038 +  destroy: function() {
  1.1039 +    // Reject all outstanding requests, they won't make sense after
  1.1040 +    // the front is destroyed.
  1.1041 +    while (this._requests && this._requests.length > 0) {
  1.1042 +      let deferred = this._requests.shift();
  1.1043 +      deferred.reject(new Error("Connection closed"));
  1.1044 +    }
  1.1045 +    Pool.prototype.destroy.call(this);
  1.1046 +    this.actorID = null;
  1.1047 +  },
  1.1048 +
  1.1049 +  /**
  1.1050 +   * @returns a promise that will resolve to the actorID this front
  1.1051 +   * represents.
  1.1052 +   */
  1.1053 +  actor: function() { return promise.resolve(this.actorID) },
  1.1054 +
  1.1055 +  toString: function() { return "[Front for " + this.typeName + "/" + this.actorID + "]" },
  1.1056 +
  1.1057 +  /**
  1.1058 +   * Update the actor from its representation.
  1.1059 +   * Subclasses should override this.
  1.1060 +   */
  1.1061 +  form: function(form) {},
  1.1062 +
  1.1063 +  /**
  1.1064 +   * Send a packet on the connection.
  1.1065 +   */
  1.1066 +  send: function(packet) {
  1.1067 +    if (packet.to) {
  1.1068 +      this.conn._transport.send(packet);
  1.1069 +    } else {
  1.1070 +      this.actor().then(actorID => {
  1.1071 +        packet.to = actorID;
  1.1072 +        this.conn._transport.send(packet);
  1.1073 +      });
  1.1074 +    }
  1.1075 +  },
  1.1076 +
  1.1077 +  /**
  1.1078 +   * Send a two-way request on the connection.
  1.1079 +   */
  1.1080 +  request: function(packet) {
  1.1081 +    let deferred = promise.defer();
  1.1082 +    this._requests.push(deferred);
  1.1083 +    this.send(packet);
  1.1084 +    return deferred.promise;
  1.1085 +  },
  1.1086 +
  1.1087 +  /**
  1.1088 +   * Handler for incoming packets from the client's actor.
  1.1089 +   */
  1.1090 +  onPacket: function(packet) {
  1.1091 +    // Pick off event packets
  1.1092 +    if (this._clientSpec.events && this._clientSpec.events.has(packet.type)) {
  1.1093 +      let event = this._clientSpec.events.get(packet.type);
  1.1094 +      let args = event.request.read(packet, this);
  1.1095 +      if (event.pre) {
  1.1096 +        event.pre.forEach((pre) => pre.apply(this, args));
  1.1097 +      }
  1.1098 +      events.emit.apply(null, [this, event.name].concat(args));
  1.1099 +      return;
  1.1100 +    }
  1.1101 +
  1.1102 +    // Remaining packets must be responses.
  1.1103 +    if (this._requests.length === 0) {
  1.1104 +      let msg = "Unexpected packet " + this.actorID + ", " + JSON.stringify(packet);
  1.1105 +      let err = Error(msg);
  1.1106 +      console.error(err);
  1.1107 +      throw err;
  1.1108 +    }
  1.1109 +
  1.1110 +    let deferred = this._requests.shift();
  1.1111 +    if (packet.error) {
  1.1112 +      deferred.reject(packet.error);
  1.1113 +    } else {
  1.1114 +      deferred.resolve(packet);
  1.1115 +    }
  1.1116 +  }
  1.1117 +});
  1.1118 +exports.Front = Front;
  1.1119 +
  1.1120 +/**
  1.1121 + * A method tagged with preEvent will be called after recieving a packet
  1.1122 + * for that event, and before the front emits the event.
  1.1123 + */
  1.1124 +exports.preEvent = function(eventName, fn) {
  1.1125 +  fn._preEvent = eventName;
  1.1126 +  return fn;
  1.1127 +}
  1.1128 +
  1.1129 +/**
  1.1130 + * Mark a method as a custom front implementation, replacing the generated
  1.1131 + * front method.
  1.1132 + *
  1.1133 + * @param function fn
  1.1134 + *    The front implementation, will be returned.
  1.1135 + * @param object options
  1.1136 + *    Options object:
  1.1137 + *      impl (string): If provided, the generated front method will be
  1.1138 + *        stored as this property on the prototype.
  1.1139 + */
  1.1140 +exports.custom = function(fn, options={}) {
  1.1141 +  fn._customFront = options;
  1.1142 +  return fn;
  1.1143 +}
  1.1144 +
  1.1145 +function prototypeOf(obj) {
  1.1146 +  return typeof(obj) === "function" ? obj.prototype : obj;
  1.1147 +}
  1.1148 +
  1.1149 +/**
  1.1150 + * Process a front definition from its prototype and generate
  1.1151 + * request methods.
  1.1152 + */
  1.1153 +let frontProto = function(proto) {
  1.1154 +  let actorType = prototypeOf(proto.actorType);
  1.1155 +  if (proto._actorSpec) {
  1.1156 +    throw new Error("frontProto called twice on the same front prototype!");
  1.1157 +  }
  1.1158 +  proto._actorSpec = actorType._actorSpec;
  1.1159 +  proto.typeName = actorType.typeName;
  1.1160 +
  1.1161 +  // Generate request methods.
  1.1162 +  let methods = proto._actorSpec.methods;
  1.1163 +  methods.forEach(spec => {
  1.1164 +    let name = spec.name;
  1.1165 +
  1.1166 +    // If there's already a property by this name in the front, it must
  1.1167 +    // be a custom front method.
  1.1168 +    if (name in proto) {
  1.1169 +      let custom = proto[spec.name]._customFront;
  1.1170 +      if (custom === undefined) {
  1.1171 +        throw Error("Existing method for " + spec.name + " not marked customFront while processing " + actorType.typeName + ".");
  1.1172 +      }
  1.1173 +      // If the user doesn't need the impl don't generate it.
  1.1174 +      if (!custom.impl) {
  1.1175 +        return;
  1.1176 +      }
  1.1177 +      name = custom.impl;
  1.1178 +    }
  1.1179 +
  1.1180 +    proto[name] = function(...args) {
  1.1181 +      let histogram, startTime;
  1.1182 +      if (spec.telemetry) {
  1.1183 +        if (spec.oneway) {
  1.1184 +          // That just doesn't make sense.
  1.1185 +          throw Error("Telemetry specified for a oneway request");
  1.1186 +        }
  1.1187 +        let transportType = this.conn.localTransport
  1.1188 +          ? "LOCAL_"
  1.1189 +          : "REMOTE_";
  1.1190 +        let histogramId = "DEVTOOLS_DEBUGGER_RDP_"
  1.1191 +          + transportType + spec.telemetry + "_MS";
  1.1192 +        try {
  1.1193 +          histogram = Services.telemetry.getHistogramById(histogramId);
  1.1194 +          startTime = new Date();
  1.1195 +        } catch(ex) {
  1.1196 +          // XXX: Is this expected in xpcshell tests?
  1.1197 +          console.error(ex);
  1.1198 +          spec.telemetry = false;
  1.1199 +        }
  1.1200 +      }
  1.1201 +
  1.1202 +      let packet = spec.request.write(args, this);
  1.1203 +      if (spec.oneway) {
  1.1204 +        // Fire-and-forget oneway packets.
  1.1205 +        this.send(packet);
  1.1206 +        return undefined;
  1.1207 +      }
  1.1208 +
  1.1209 +      return this.request(packet).then(response => {
  1.1210 +        let ret = spec.response.read(response, this);
  1.1211 +
  1.1212 +        if (histogram) {
  1.1213 +          histogram.add(+new Date - startTime);
  1.1214 +        }
  1.1215 +
  1.1216 +        return ret;
  1.1217 +      }).then(null, promiseDone);
  1.1218 +    }
  1.1219 +
  1.1220 +    // Release methods should call the destroy function on return.
  1.1221 +    if (spec.release) {
  1.1222 +      let fn = proto[name];
  1.1223 +      proto[name] = function(...args) {
  1.1224 +        return fn.apply(this, args).then(result => {
  1.1225 +          this.destroy();
  1.1226 +          return result;
  1.1227 +        })
  1.1228 +      }
  1.1229 +    }
  1.1230 +  });
  1.1231 +
  1.1232 +
  1.1233 +  // Process event specifications
  1.1234 +  proto._clientSpec = {};
  1.1235 +
  1.1236 +  let events = proto._actorSpec.events;
  1.1237 +  if (events) {
  1.1238 +    // This actor has events, scan the prototype for preEvent handlers...
  1.1239 +    let preHandlers = new Map();
  1.1240 +    for (let name of Object.getOwnPropertyNames(proto)) {
  1.1241 +      let desc = Object.getOwnPropertyDescriptor(proto, name);
  1.1242 +      if (!desc.value) {
  1.1243 +        continue;
  1.1244 +      }
  1.1245 +      if (desc.value._preEvent) {
  1.1246 +        let preEvent = desc.value._preEvent;
  1.1247 +        if (!events.has(preEvent)) {
  1.1248 +          throw Error("preEvent for event that doesn't exist: " + preEvent);
  1.1249 +        }
  1.1250 +        let handlers = preHandlers.get(preEvent);
  1.1251 +        if (!handlers) {
  1.1252 +          handlers = [];
  1.1253 +          preHandlers.set(preEvent, handlers);
  1.1254 +        }
  1.1255 +        handlers.push(desc.value);
  1.1256 +      }
  1.1257 +    }
  1.1258 +
  1.1259 +    proto._clientSpec.events = new Map();
  1.1260 +
  1.1261 +    for (let [name, request] of events) {
  1.1262 +      proto._clientSpec.events.set(request.type, {
  1.1263 +        name: name,
  1.1264 +        request: request,
  1.1265 +        pre: preHandlers.get(name)
  1.1266 +      });
  1.1267 +    }
  1.1268 +  }
  1.1269 +  return proto;
  1.1270 +}
  1.1271 +
  1.1272 +/**
  1.1273 + * Create a front class for the given actor class, with the given prototype.
  1.1274 + *
  1.1275 + * @param ActorClass actorType
  1.1276 + *    The actor class you're creating a front for.
  1.1277 + * @param object proto
  1.1278 + *    The object prototype.  Must have a 'typeName' property,
  1.1279 + *    should have method definitions, can have event definitions.
  1.1280 + */
  1.1281 +exports.FrontClass = function(actorType, proto) {
  1.1282 +  proto.actorType = actorType;
  1.1283 +  proto.extends = Front;
  1.1284 +  let cls = Class(frontProto(proto));
  1.1285 +  registeredTypes.get(cls.prototype.typeName).frontClass = cls;
  1.1286 +  return cls;
  1.1287 +}
  1.1288 +
  1.1289 +
  1.1290 +exports.dumpActorSpec = function(type) {
  1.1291 +  let actorSpec = type.actorSpec;
  1.1292 +  let ret = {
  1.1293 +    category: "actor",
  1.1294 +    typeName: type.name,
  1.1295 +    methods: [],
  1.1296 +    events: {}
  1.1297 +  };
  1.1298 +
  1.1299 +  for (let method of actorSpec.methods) {
  1.1300 +    ret.methods.push({
  1.1301 +      name: method.name,
  1.1302 +      release: method.release || undefined,
  1.1303 +      oneway: method.oneway || undefined,
  1.1304 +      request: method.request.describe(),
  1.1305 +      response: method.response.describe()
  1.1306 +    });
  1.1307 +  }
  1.1308 +
  1.1309 +  if (actorSpec.events) {
  1.1310 +    for (let [name, request] of actorSpec.events) {
  1.1311 +      ret.events[name] = request.describe();
  1.1312 +    }
  1.1313 +  }
  1.1314 +
  1.1315 +
  1.1316 +  JSON.stringify(ret);
  1.1317 +
  1.1318 +  return ret;
  1.1319 +}
  1.1320 +
  1.1321 +exports.dumpProtocolSpec = function() {
  1.1322 +  let ret = {
  1.1323 +    types: {},
  1.1324 +  };
  1.1325 +
  1.1326 +  for (let [name, type] of registeredTypes) {
  1.1327 +    // Force lazy instantiation if needed.
  1.1328 +    type = types.getType(name);
  1.1329 +    if (type.category === "dict") {
  1.1330 +      ret.types[name] = {
  1.1331 +        category: "dict",
  1.1332 +        typeName: name,
  1.1333 +        specializations: type.specializations
  1.1334 +      }
  1.1335 +    } else if (type.category === "actor") {
  1.1336 +      ret.types[name] = exports.dumpActorSpec(type);
  1.1337 +    }
  1.1338 +  }
  1.1339 +
  1.1340 +  return ret;
  1.1341 +}

mercurial