diff options
Diffstat (limited to 'addon-sdk/source/examples/debug-client/data/client.js')
-rw-r--r-- | addon-sdk/source/examples/debug-client/data/client.js | 816 |
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); + |