summaryrefslogtreecommitdiffstats
path: root/addon-sdk/source/examples/debug-client/data/client.js
diff options
context:
space:
mode:
Diffstat (limited to 'addon-sdk/source/examples/debug-client/data/client.js')
-rw-r--r--addon-sdk/source/examples/debug-client/data/client.js816
1 files changed, 816 insertions, 0 deletions
diff --git a/addon-sdk/source/examples/debug-client/data/client.js b/addon-sdk/source/examples/debug-client/data/client.js
new file mode 100644
index 000000000..022f9a1c3
--- /dev/null
+++ b/addon-sdk/source/examples/debug-client/data/client.js
@@ -0,0 +1,816 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+(function(exports) {
+"use strict";
+
+
+var describe = Object.getOwnPropertyDescriptor;
+var Class = fields => {
+ var constructor = fields.constructor || function() {};
+ var ancestor = fields.extends || Object;
+
+
+
+ var descriptor = {};
+ for (var key of Object.keys(fields))
+ descriptor[key] = describe(fields, key);
+
+ var prototype = Object.create(ancestor.prototype, descriptor);
+
+ constructor.prototype = prototype;
+ prototype.constructor = constructor;
+
+ return constructor;
+};
+
+
+var bus = function Bus() {
+ var parser = new DOMParser();
+ return parser.parseFromString("<EventTarget/>", "application/xml").documentElement;
+}();
+
+var GUID = new WeakMap();
+GUID.id = 0;
+var guid = x => GUID.get(x);
+var setGUID = x => {
+ GUID.set(x, ++ GUID.id);
+};
+
+var Emitter = Class({
+ extends: EventTarget,
+ constructor: function() {
+ this.setupEmitter();
+ },
+ setupEmitter: function() {
+ setGUID(this);
+ },
+ addEventListener: function(type, listener, capture) {
+ bus.addEventListener(type + "@" + guid(this),
+ listener, capture);
+ },
+ removeEventListener: function(type, listener, capture) {
+ bus.removeEventListener(type + "@" + guid(this),
+ listener, capture);
+ }
+});
+
+function dispatch(target, type, data) {
+ var event = new MessageEvent(type + "@" + guid(target), {
+ bubbles: true,
+ cancelable: false,
+ data: data
+ });
+ bus.dispatchEvent(event);
+}
+
+var supervisedWorkers = new WeakMap();
+var supervised = supervisor => {
+ if (!supervisedWorkers.has(supervisor)) {
+ supervisedWorkers.set(supervisor, new Map());
+ supervisor.connection.addActorPool(supervisor);
+ }
+ return supervisedWorkers.get(supervisor);
+};
+
+var Supervisor = Class({
+ extends: Emitter,
+ constructor: function(...params) {
+ this.setupEmitter(...params);
+ this.setupSupervisor(...params);
+ },
+ Supervisor: function(connection) {
+ this.connection = connection;
+ },
+ /**
+ * Return the parent pool for this client.
+ */
+ supervisor: function() {
+ return this.connection.poolFor(this.actorID);
+ },
+ /**
+ * Override this if you want actors returned by this actor
+ * to belong to a different actor by default.
+ */
+ marshallPool: function() { return this; },
+ /**
+ * Add an actor as a child of this pool.
+ */
+ supervise: function(actor) {
+ if (!actor.actorID)
+ actor.actorID = this.connection.allocID(actor.actorPrefix ||
+ actor.typeName);
+
+ supervised(this).set(actor.actorID, actor);
+ return actor;
+ },
+ /**
+ * Remove an actor as a child of this pool.
+ */
+ abandon: function(actor) {
+ supervised(this).delete(actor.actorID);
+ },
+ // true if the given actor ID exists in the pool.
+ has: function(actorID) {
+ return supervised(this).has(actorID);
+ },
+ // Same as actor, should update debugger connection to use 'actor'
+ // and then remove this.
+ get: function(actorID) {
+ return supervised(this).get(actorID);
+ },
+ actor: function(actorID) {
+ return supervised(this).get(actorID);
+ },
+ isEmpty: function() {
+ return supervised(this).size === 0;
+ },
+ /**
+ * For getting along with the debugger server pools, should be removable
+ * eventually.
+ */
+ cleanup: function() {
+ this.destroy();
+ },
+ destroy: function() {
+ var supervisor = this.supervisor();
+ if (supervisor)
+ supervisor.abandon(this);
+
+ for (var actor of supervised(this).values()) {
+ if (actor !== this) {
+ var destroy = actor.destroy;
+ // Disconnect destroy while we're destroying in case of (misbehaving)
+ // circular ownership.
+ if (destroy) {
+ actor.destroy = null;
+ destroy.call(actor);
+ actor.destroy = destroy;
+ }
+ }
+ }
+
+ this.connection.removeActorPool(this);
+ supervised(this).clear();
+ }
+
+});
+
+
+
+
+var mailbox = new WeakMap();
+var clientRequests = new WeakMap();
+
+var inbox = client => mailbox.get(client).inbox;
+var outbox = client => mailbox.get(client).outbox;
+var requests = client => clientRequests.get(client);
+
+
+var Receiver = Class({
+ receive: function(packet) {
+ if (packet.error)
+ this.reject(packet.error);
+ else
+ this.resolve(this.read(packet));
+ }
+});
+
+var Connection = Class({
+ constructor: function() {
+ // Queue of the outgoing messages.
+ this.outbox = [];
+ // Map of pending requests.
+ this.pending = new Map();
+ this.pools = new Set();
+ },
+ isConnected: function() {
+ return !!this.port
+ },
+ connect: function(port) {
+ this.port = port;
+ port.addEventListener("message", this);
+ port.start();
+
+ this.flush();
+ },
+ addPool: function(pool) {
+ this.pools.add(pool);
+ },
+ removePool: function(pool) {
+ this.pools.delete(pool);
+ },
+ poolFor: function(id) {
+ for (let pool of this.pools.values()) {
+ if (pool.has(id))
+ return pool;
+ }
+ },
+ get: function(id) {
+ var pool = this.poolFor(id);
+ return pool && pool.get(id);
+ },
+ disconnect: function() {
+ this.port.stop();
+ this.port = null;
+ for (var request of this.pending.values()) {
+ request.catch(new Error("Connection closed"));
+ }
+ this.pending.clear();
+
+ var requests = this.outbox.splice(0);
+ for (var request of request) {
+ requests.catch(new Error("Connection closed"));
+ }
+ },
+ handleEvent: function(event) {
+ this.receive(event.data);
+ },
+ flush: function() {
+ if (this.isConnected()) {
+ for (var request of this.outbox) {
+ if (!this.pending.has(request.to)) {
+ this.outbox.splice(this.outbox.indexOf(request), 1);
+ this.pending.set(request.to, request);
+ this.send(request.packet);
+ }
+ }
+ }
+ },
+ send: function(packet) {
+ this.port.postMessage(packet);
+ },
+ request: function(packet) {
+ return new Promise(function(resolve, reject) {
+ this.outbox.push({
+ to: packet.to,
+ packet: packet,
+ receive: resolve,
+ catch: reject
+ });
+ this.flush();
+ });
+ },
+ receive: function(packet) {
+ var { from, type, why } = packet;
+ var receiver = this.pending.get(from);
+ if (!receiver) {
+ console.warn("Unable to handle received packet", data);
+ } else {
+ this.pending.delete(from);
+ if (packet.error)
+ receiver.catch(packet.error);
+ else
+ receiver.receive(packet);
+ }
+ this.flush();
+ },
+});
+
+/**
+ * Base class for client-side actor fronts.
+ */
+var Client = Class({
+ extends: Supervisor,
+ constructor: function(from=null, detail=null, connection=null) {
+ this.Client(from, detail, connection);
+ },
+ Client: function(form, detail, connection) {
+ this.Supervisor(connection);
+
+ if (form) {
+ this.actorID = form.actor;
+ this.from(form, detail);
+ }
+ },
+ connect: function(port) {
+ this.connection = new Connection(port);
+ },
+ actorID: null,
+ actor: function() {
+ return this.actorID;
+ },
+ /**
+ * Update the actor from its representation.
+ * Subclasses should override this.
+ */
+ form: function(form) {
+ },
+ /**
+ * Method is invokeid when packet received constitutes an
+ * event. By default such packets are demarshalled and
+ * dispatched on the client instance.
+ */
+ dispatch: function(packet) {
+ },
+ /**
+ * Method is invoked when packet is returned in response to
+ * a request. By default respond delivers response to a first
+ * request in a queue.
+ */
+ read: function(input) {
+ throw new TypeError("Subclass must implement read method");
+ },
+ write: function(input) {
+ throw new TypeError("Subclass must implement write method");
+ },
+ respond: function(packet) {
+ var [resolve, reject] = requests(this).shift();
+ if (packet.error)
+ reject(packet.error);
+ else
+ resolve(this.read(packet));
+ },
+ receive: function(packet) {
+ if (this.isEventPacket(packet)) {
+ this.dispatch(packet);
+ }
+ else if (requests(this).length) {
+ this.respond(packet);
+ }
+ else {
+ this.catch(packet);
+ }
+ },
+ send: function(packet) {
+ Promise.cast(packet.to || this.actor()).then(id => {
+ packet.to = id;
+ this.connection.send(packet);
+ })
+ },
+ request: function(packet) {
+ return this.connection.request(packet);
+ }
+});
+
+
+var Destructor = method => {
+ return function(...args) {
+ return method.apply(this, args).then(result => {
+ this.destroy();
+ return result;
+ });
+ };
+};
+
+var Profiled = (method, id) => {
+ return function(...args) {
+ var start = new Date();
+ return method.apply(this, args).then(result => {
+ var end = new Date();
+ this.telemetry.add(id, +end - start);
+ return result;
+ });
+ };
+};
+
+var Method = (request, response) => {
+ return response ? new BidirectionalMethod(request, response) :
+ new UnidirecationalMethod(request);
+};
+
+var UnidirecationalMethod = request => {
+ return function(...args) {
+ var packet = request.write(args, this);
+ this.connection.send(packet);
+ return Promise.resolve(void(0));
+ };
+};
+
+var BidirectionalMethod = (request, response) => {
+ return function(...args) {
+ var packet = request.write(args, this);
+ return this.connection.request(packet).then(packet => {
+ return response.read(packet, this);
+ });
+ };
+};
+
+
+Client.from = ({category, typeName, methods, events}) => {
+ var proto = {
+ constructor: function(...args) {
+ this.Client(...args);
+ },
+ extends: Client,
+ name: typeName
+ };
+
+ methods.forEach(({telemetry, request, response, name, oneway, release}) => {
+ var [reader, writer] = oneway ? [, new Request(request)] :
+ [new Request(request), new Response(response)];
+ var method = new Method(request, response);
+ var profiler = telemetry ? new Profiler(method) : method;
+ var destructor = release ? new Destructor(profiler) : profiler;
+ proto[name] = destructor;
+ });
+
+ return Class(proto);
+};
+
+
+var defineType = (client, descriptor) => {
+ var type = void(0)
+ if (typeof(descriptor) === "string") {
+ if (name.indexOf(":") > 0)
+ type = makeCompoundType(descriptor);
+ else if (name.indexOf("#") > 0)
+ type = new ActorDetail(descriptor);
+ else if (client.specification[descriptor])
+ type = makeCategoryType(client.specification[descriptor]);
+ } else {
+ type = makeCategoryType(descriptor);
+ }
+
+ if (type)
+ client.types.set(type.name, type);
+ else
+ throw TypeError("Invalid type: " + descriptor);
+};
+
+
+var makeCompoundType = name => {
+ var index = name.indexOf(":");
+ var [baseType, subType] = [name.slice(0, index), parts.slice(1)];
+ return baseType === "array" ? new ArrayOf(subType) :
+ baseType === "nullable" ? new Maybe(subType) :
+ null;
+};
+
+var makeCategoryType = (descriptor) => {
+ var { category } = descriptor;
+ return category === "dict" ? new Dictionary(descriptor) :
+ category === "actor" ? new Actor(descriptor) :
+ null;
+};
+
+
+var typeFor = (client, type="primitive") => {
+ if (!client.types.has(type))
+ defineType(client, type);
+
+ return client.types.get(type);
+};
+
+
+var Client = Class({
+ constructor: function() {
+ },
+ setupTypes: function(specification) {
+ this.specification = specification;
+ this.types = new Map();
+ },
+ read: function(input, type) {
+ return typeFor(this, type).read(input, this);
+ },
+ write: function(input, type) {
+ return typeFor(this, type).write(input, this);
+ }
+});
+
+
+var Type = Class({
+ get name() {
+ return this.category ? this.category + ":" + this.type :
+ this.type;
+ },
+ read: function(input, client) {
+ throw new TypeError("`Type` subclass must implement `read`");
+ },
+ write: function(input, client) {
+ throw new TypeError("`Type` subclass must implement `write`");
+ }
+});
+
+
+var Primitve = Class({
+ extends: Type,
+ constuctor: function(type) {
+ this.type = type;
+ },
+ read: function(input, client) {
+ return input;
+ },
+ write: function(input, client) {
+ return input;
+ }
+});
+
+var Maybe = Class({
+ extends: Type,
+ category: "nullable",
+ constructor: function(type) {
+ this.type = type;
+ },
+ read: function(input, client) {
+ return input === null ? null :
+ input === void(0) ? void(0) :
+ client.read(input, this.type);
+ },
+ write: function(input, client) {
+ return input === null ? null :
+ input === void(0) ? void(0) :
+ client.write(input, this.type);
+ }
+});
+
+var ArrayOf = Class({
+ extends: Type,
+ category: "array",
+ constructor: function(type) {
+ this.type = type;
+ },
+ read: function(input, client) {
+ return input.map($ => client.read($, this.type));
+ },
+ write: function(input, client) {
+ return input.map($ => client.write($, this.type));
+ }
+});
+
+var Dictionary = Class({
+ exteds: Type,
+ category: "dict",
+ get name() { return this.type; },
+ constructor: function({typeName, specializations}) {
+ this.type = typeName;
+ this.types = specifications;
+ },
+ read: function(input, client) {
+ var output = {};
+ for (var key in input) {
+ output[key] = client.read(input[key], this.types[key]);
+ }
+ return output;
+ },
+ write: function(input, client) {
+ var output = {};
+ for (var key in input) {
+ output[key] = client.write(value, this.types[key]);
+ }
+ return output;
+ }
+});
+
+var Actor = Class({
+ exteds: Type,
+ category: "actor",
+ get name() { return this.type; },
+ constructor: function({typeName}) {
+ this.type = typeName;
+ },
+ read: function(input, client, detail) {
+ var id = value.actor;
+ var actor = void(0);
+ if (client.connection.has(id)) {
+ return client.connection.get(id).form(input, detail, client);
+ } else {
+ actor = Client.from(detail, client);
+ actor.actorID = id;
+ client.supervise(actor);
+ }
+ },
+ write: function(input, client, detail) {
+ if (input instanceof Actor) {
+ if (!input.actorID) {
+ client.supervise(input);
+ }
+ return input.from(detail);
+ }
+ return input.actorID;
+ }
+});
+
+var Root = Client.from({
+ "category": "actor",
+ "typeName": "root",
+ "methods": [
+ {"name": "listTabs",
+ "request": {},
+ "response": {
+ }
+ },
+ {"name": "listAddons"
+ },
+ {"name": "echo",
+
+ },
+ {"name": "protocolDescription",
+
+ }
+ ]
+});
+
+
+var ActorDetail = Class({
+ extends: Actor,
+ constructor: function(name, actor, detail) {
+ this.detail = detail;
+ this.actor = actor;
+ },
+ read: function(input, client) {
+ this.actor.read(input, client, this.detail);
+ },
+ write: function(input, client) {
+ this.actor.write(input, client, this.detail);
+ }
+});
+
+var registeredLifetimes = new Map();
+var LifeTime = Class({
+ extends: Type,
+ category: "lifetime",
+ constructor: function(lifetime, type) {
+ this.name = lifetime + ":" + type.name;
+ this.field = registeredLifetimes.get(lifetime);
+ },
+ read: function(input, client) {
+ return this.type.read(input, client[this.field]);
+ },
+ write: function(input, client) {
+ return this.type.write(input, client[this.field]);
+ }
+});
+
+var primitive = new Primitve("primitive");
+var string = new Primitve("string");
+var number = new Primitve("number");
+var boolean = new Primitve("boolean");
+var json = new Primitve("json");
+var array = new Primitve("array");
+
+
+var TypedValue = Class({
+ extends: Type,
+ constructor: function(name, type) {
+ this.TypedValue(name, type);
+ },
+ TypedValue: function(name, type) {
+ this.name = name;
+ this.type = type;
+ },
+ read: function(input, client) {
+ return this.client.read(input, this.type);
+ },
+ write: function(input, client) {
+ return this.client.write(input, this.type);
+ }
+});
+
+var Return = Class({
+ extends: TypedValue,
+ constructor: function(type) {
+ this.type = type
+ }
+});
+
+var Argument = Class({
+ extends: TypedValue,
+ constructor: function(...args) {
+ this.Argument(...args);
+ },
+ Argument: function(index, type) {
+ this.index = index;
+ this.TypedValue("argument[" + index + "]", type);
+ },
+ read: function(input, client, target) {
+ return target[this.index] = client.read(input, this.type);
+ }
+});
+
+var Option = Class({
+ extends: Argument,
+ constructor: function(...args) {
+ return this.Argument(...args);
+ },
+ read: function(input, client, target, name) {
+ var param = target[this.index] || (target[this.index] = {});
+ param[name] = input === void(0) ? input : client.read(input, this.type);
+ },
+ write: function(input, client, name) {
+ var value = input && input[name];
+ return value === void(0) ? value : client.write(value, this.type);
+ }
+});
+
+var Request = Class({
+ extends: Type,
+ constructor: function(template={}) {
+ this.type = template.type;
+ this.template = template;
+ this.params = findPlaceholders(template, Argument);
+ },
+ read: function(packet, client) {
+ var args = [];
+ for (var param of this.params) {
+ var {placeholder, path} = param;
+ var name = path[path.length - 1];
+ placeholder.read(getPath(packet, path), client, args, name);
+ // TODO:
+ // args[placeholder.index] = placeholder.read(query(packet, path), client);
+ }
+ return args;
+ },
+ write: function(input, client) {
+ return JSON.parse(JSON.stringify(this.template, (key, value) => {
+ return value instanceof Argument ? value.write(input[value.index],
+ client, key) :
+ value;
+ }));
+ }
+});
+
+var Response = Class({
+ extends: Type,
+ constructor: function(template={}) {
+ this.template = template;
+ var [x] = findPlaceholders(template, Return);
+ var {placeholder, path} = x;
+ this.return = placeholder;
+ this.path = path;
+ },
+ read: function(packet, client) {
+ var value = query(packet, this.path);
+ return this.return.read(value, client);
+ },
+ write: function(input, client) {
+ return JSON.parse(JSON.stringify(this.template, (key, value) => {
+ return value instanceof Return ? value.write(input) :
+ input
+ }));
+ }
+});
+
+// Returns array of values for the given object.
+var values = object => Object.keys(object).map(key => object[key]);
+// Returns [key, value] pairs for the given object.
+var pairs = object => Object.keys(object).map(key => [key, object[key]]);
+// Queries an object for the field nested with in it.
+var query = (object, path) => path.reduce((object, entry) => object && object[entry],
+ object);
+
+
+var Root = Client.from({
+ "category": "actor",
+ "typeName": "root",
+ "methods": [
+ {
+ "name": "echo",
+ "request": {
+ "string": { "_arg": 0, "type": "string" }
+ },
+ "response": {
+ "string": { "_retval": "string" }
+ }
+ },
+ {
+ "name": "listTabs",
+ "request": {},
+ "response": { "_retval": "tablist" }
+ },
+ {
+ "name": "actorDescriptions",
+ "request": {},
+ "response": { "_retval": "json" }
+ }
+ ],
+ "events": {
+ "tabListChanged": {}
+ }
+});
+
+var Tab = Client.from({
+ "category": "dict",
+ "typeName": "tab",
+ "specifications": {
+ "title": "string",
+ "url": "string",
+ "outerWindowID": "number",
+ "console": "console",
+ "inspectorActor": "inspector",
+ "callWatcherActor": "call-watcher",
+ "canvasActor": "canvas",
+ "webglActor": "webgl",
+ "webaudioActor": "webaudio",
+ "styleSheetsActor": "stylesheets",
+ "styleEditorActor": "styleeditor",
+ "storageActor": "storage",
+ "gcliActor": "gcli",
+ "memoryActor": "memory",
+ "eventLoopLag": "eventLoopLag",
+
+ "trace": "trace", // missing
+ }
+});
+
+var tablist = Client.from({
+ "category": "dict",
+ "typeName": "tablist",
+ "specializations": {
+ "selected": "number",
+ "tabs": "array:tab"
+ }
+});
+
+})(this);
+