diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /addon-sdk/source/examples/debug-client | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'addon-sdk/source/examples/debug-client')
-rw-r--r-- | addon-sdk/source/examples/debug-client/data/client.js | 816 | ||||
-rw-r--r-- | addon-sdk/source/examples/debug-client/data/index.html | 50 | ||||
-rw-r--r-- | addon-sdk/source/examples/debug-client/data/plugin.png | bin | 0 -> 3819 bytes | |||
-rw-r--r-- | addon-sdk/source/examples/debug-client/data/task.js | 28 | ||||
-rw-r--r-- | addon-sdk/source/examples/debug-client/index.js | 33 | ||||
-rw-r--r-- | addon-sdk/source/examples/debug-client/package.json | 10 | ||||
-rw-r--r-- | addon-sdk/source/examples/debug-client/test/test-main.js | 10 |
7 files changed, 947 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); + diff --git a/addon-sdk/source/examples/debug-client/data/index.html b/addon-sdk/source/examples/debug-client/data/index.html new file mode 100644 index 000000000..7788e3580 --- /dev/null +++ b/addon-sdk/source/examples/debug-client/data/index.html @@ -0,0 +1,50 @@ +<!-- 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/. --> +<html> + <head> + <script src="resource://sdk/dev/volcan.js"></script> + <script src="./task.js"></script> + </head> + <body> + </body> + <script> + const wait = (target, type, capture) => new Promise((resolve, reject) => { + const listener = event => { + target.removeEventListener(type, listener, capture); + resolve(event); + }; + target.addEventListener(type, listener, capture); + }); + + const display = message => + document.body.innerHTML += message + "<br/>"; + + Task.spawn(function*() { + var event = yield wait(window, "message"); + var port = event.ports[0]; + + display("Port received"); + var root = yield volcan.connect(port); + + display("Connected to a debugger"); + + var message = yield root.echo("hello") + + display("Received echo for: " + message); + + var list = yield root.listTabs(); + + display("You have " + list.tabs.length + " open tabs"); + + var activeTab = list.tabs[list.selected]; + + display("Your active tab url is: " + activeTab.url); + + var sheets = yield activeTab.styleSheetsActor.getStyleSheets(); + + display("Page in active tab has " + sheets.length + " stylesheets"); + + }); + </script> +</html> diff --git a/addon-sdk/source/examples/debug-client/data/plugin.png b/addon-sdk/source/examples/debug-client/data/plugin.png Binary files differnew file mode 100644 index 000000000..6a364a30a --- /dev/null +++ b/addon-sdk/source/examples/debug-client/data/plugin.png diff --git a/addon-sdk/source/examples/debug-client/data/task.js b/addon-sdk/source/examples/debug-client/data/task.js new file mode 100644 index 000000000..b46feb10e --- /dev/null +++ b/addon-sdk/source/examples/debug-client/data/task.js @@ -0,0 +1,28 @@ +/* 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"; + +const spawn = (task, ...args) => { + return new Promise((resolve, reject) => { + try { + const routine = task(...args); + const raise = error => routine.throw(error); + const step = data => { + const { done, value } = routine.next(data); + if (done) + resolve(value); + else + Promise.resolve(value).then(step, raise); + } + step(); + } catch(error) { + reject(error); + } + }); +} +exports.spawn = spawn; + +})(Task = {}); diff --git a/addon-sdk/source/examples/debug-client/index.js b/addon-sdk/source/examples/debug-client/index.js new file mode 100644 index 000000000..ff91ff8cd --- /dev/null +++ b/addon-sdk/source/examples/debug-client/index.js @@ -0,0 +1,33 @@ +/* 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/. */ +"use strict"; + +const { Panel } = require("dev/panel"); +const { Tool } = require("dev/toolbox"); +const { Class } = require("sdk/core/heritage"); + + +const LadybugPanel = Class({ + extends: Panel, + label: "Ladybug", + tooltip: "Debug client example", + icon: "./plugin.png", + url: "./index.html", + setup: function({debuggee}) { + this.debuggee = debuggee; + }, + dispose: function() { + delete this.debuggee; + }, + onReady: function() { + this.debuggee.start(); + this.postMessage("RDP", [this.debuggee]); + }, +}); +exports.LadybugPanel = LadybugPanel; + + +const ladybug = new Tool({ + panels: { ladybug: LadybugPanel } +}); diff --git a/addon-sdk/source/examples/debug-client/package.json b/addon-sdk/source/examples/debug-client/package.json new file mode 100644 index 000000000..058fa97af --- /dev/null +++ b/addon-sdk/source/examples/debug-client/package.json @@ -0,0 +1,10 @@ +{ + "name": "debug-client", + "id": "@debug-client", + "title": "Debug client", + "description": "Example debug client", + "version": "0.0.1", + "author": "Irakli Gozalishvili", + "main": "./index.js", + "license": "MPL-2.0" +} diff --git a/addon-sdk/source/examples/debug-client/test/test-main.js b/addon-sdk/source/examples/debug-client/test/test-main.js new file mode 100644 index 000000000..9862fc20b --- /dev/null +++ b/addon-sdk/source/examples/debug-client/test/test-main.js @@ -0,0 +1,10 @@ +/* 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/. */ + "use strict"; + +exports.testMain = function(assert) { + assert.pass("TODO: Write some tests."); +}; + +require("sdk/test").run(exports); |