diff options
author | Matt A. Tobin <email@mattatobin.com> | 2018-02-09 06:46:43 -0500 |
---|---|---|
committer | Matt A. Tobin <email@mattatobin.com> | 2018-02-09 06:46:43 -0500 |
commit | ac46df8daea09899ce30dc8fd70986e258c746bf (patch) | |
tree | 2750d3125fc253fd5b0671e4bd268eff1fd97296 /addon-sdk/source/lib/sdk/event | |
parent | 8cecf8d5208f3945b35f879bba3015bb1a11bec6 (diff) | |
download | UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.gz UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.lz UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.xz UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.zip |
Move Add-on SDK source to toolkit/jetpack
Diffstat (limited to 'addon-sdk/source/lib/sdk/event')
-rw-r--r-- | addon-sdk/source/lib/sdk/event/chrome.js | 65 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/event/core.js | 193 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/event/dom.js | 78 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/event/target.js | 74 | ||||
-rw-r--r-- | addon-sdk/source/lib/sdk/event/utils.js | 328 |
5 files changed, 0 insertions, 738 deletions
diff --git a/addon-sdk/source/lib/sdk/event/chrome.js b/addon-sdk/source/lib/sdk/event/chrome.js deleted file mode 100644 index 9044fef99..000000000 --- a/addon-sdk/source/lib/sdk/event/chrome.js +++ /dev/null @@ -1,65 +0,0 @@ -/* 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"; - -module.metadata = { - "stability": "unstable" -}; - -const { Cc, Ci, Cr, Cu } = require("chrome"); -const { emit, on, off } = require("./core"); -var observerService = Cc["@mozilla.org/observer-service;1"] - .getService(Ci.nsIObserverService); - -const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm"); -const addObserver = ShimWaiver.getProperty(observerService, "addObserver"); -const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver"); - -const { when: unload } = require("../system/unload"); - -// Simple class that can be used to instantiate event channel that -// implements `nsIObserver` interface. It's will is used by `observe` -// function as observer + event target. It basically proxies observer -// notifications as to it's registered listeners. -function ObserverChannel() {} -Object.freeze(Object.defineProperties(ObserverChannel.prototype, { - QueryInterface: { - value: function(iid) { - if (!iid.equals(Ci.nsIObserver) && - !iid.equals(Ci.nsISupportsWeakReference) && - !iid.equals(Ci.nsISupports)) - throw Cr.NS_ERROR_NO_INTERFACE; - return this; - } - }, - observe: { - value: function(subject, topic, data) { - emit(this, "data", { - type: topic, - target: subject, - data: data - }); - } - } -})); - -function observe(topic) { - let observerChannel = new ObserverChannel(); - - // Note: `nsIObserverService` will not hold a weak reference to a - // observerChannel (since third argument is `true`). There for if it - // will be GC-ed with all it's event listeners once no other references - // will be held. - addObserver(observerChannel, topic, true); - - // We need to remove any observer added once the add-on is unloaded; - // otherwise we'll get a "dead object" exception. - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1001833 - unload(() => removeObserver(observerChannel, topic)); - - return observerChannel; -} - -exports.observe = observe; diff --git a/addon-sdk/source/lib/sdk/event/core.js b/addon-sdk/source/lib/sdk/event/core.js deleted file mode 100644 index c16dd2df5..000000000 --- a/addon-sdk/source/lib/sdk/event/core.js +++ /dev/null @@ -1,193 +0,0 @@ -/* 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"; - -module.metadata = { - "stability": "unstable" -}; - -const UNCAUGHT_ERROR = 'An error event was emitted for which there was no listener.'; -const BAD_LISTENER = 'The event listener must be a function.'; - -const { ns } = require('../core/namespace'); - -const event = ns(); - -const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/; -exports.EVENT_TYPE_PATTERN = EVENT_TYPE_PATTERN; - -// Utility function to access given event `target` object's event listeners for -// the specific event `type`. If listeners for this type does not exists they -// will be created. -const observers = function observers(target, type) { - if (!target) throw TypeError("Event target must be an object"); - let listeners = event(target); - return type in listeners ? listeners[type] : listeners[type] = []; -}; - -/** - * Registers an event `listener` that is called every time events of - * specified `type` is emitted on the given event `target`. - * @param {Object} target - * Event target object. - * @param {String} type - * The type of event. - * @param {Function} listener - * The listener function that processes the event. - */ -function on(target, type, listener) { - if (typeof(listener) !== 'function') - throw new Error(BAD_LISTENER); - - let listeners = observers(target, type); - if (!~listeners.indexOf(listener)) - listeners.push(listener); -} -exports.on = on; - - -var onceWeakMap = new WeakMap(); - - -/** - * Registers an event `listener` that is called only the next time an event - * of the specified `type` is emitted on the given event `target`. - * @param {Object} target - * Event target object. - * @param {String} type - * The type of the event. - * @param {Function} listener - * The listener function that processes the event. - */ -function once(target, type, listener) { - let replacement = function observer(...args) { - off(target, type, observer); - onceWeakMap.delete(listener); - listener.apply(target, args); - }; - onceWeakMap.set(listener, replacement); - on(target, type, replacement); -} -exports.once = once; - -/** - * Execute each of the listeners in order with the supplied arguments. - * All the exceptions that are thrown by listeners during the emit - * are caught and can be handled by listeners of 'error' event. Thrown - * exceptions are passed as an argument to an 'error' event listener. - * If no 'error' listener is registered exception will be logged into an - * error console. - * @param {Object} target - * Event target object. - * @param {String} type - * The type of event. - * @params {Object|Number|String|Boolean} args - * Arguments that will be passed to listeners. - */ -function emit (target, type, ...args) { - emitOnObject(target, type, target, ...args); -} -exports.emit = emit; - -/** - * A variant of emit that allows setting the this property for event listeners - */ -function emitOnObject(target, type, thisArg, ...args) { - let all = observers(target, '*').length; - let state = observers(target, type); - let listeners = state.slice(); - let count = listeners.length; - let index = 0; - - // If error event and there are no handlers (explicit or catch-all) - // then print error message to the console. - if (count === 0 && type === 'error' && all === 0) - console.exception(args[0]); - while (index < count) { - try { - let listener = listeners[index]; - // Dispatch only if listener is still registered. - if (~state.indexOf(listener)) - listener.apply(thisArg, args); - } - catch (error) { - // If exception is not thrown by a error listener and error listener is - // registered emit `error` event. Otherwise dump exception to the console. - if (type !== 'error') emit(target, 'error', error); - else console.exception(error); - } - index++; - } - // Also emit on `"*"` so that one could listen for all events. - if (type !== '*') emit(target, '*', type, ...args); -} -exports.emitOnObject = emitOnObject; - -/** - * Removes an event `listener` for the given event `type` on the given event - * `target`. If no `listener` is passed removes all listeners of the given - * `type`. If `type` is not passed removes all the listeners of the given - * event `target`. - * @param {Object} target - * The event target object. - * @param {String} type - * The type of event. - * @param {Function} listener - * The listener function that processes the event. - */ -function off(target, type, listener) { - let length = arguments.length; - if (length === 3) { - if (onceWeakMap.has(listener)) { - listener = onceWeakMap.get(listener); - onceWeakMap.delete(listener); - } - - let listeners = observers(target, type); - let index = listeners.indexOf(listener); - if (~index) - listeners.splice(index, 1); - } - else if (length === 2) { - observers(target, type).splice(0); - } - else if (length === 1) { - let listeners = event(target); - Object.keys(listeners).forEach(type => delete listeners[type]); - } -} -exports.off = off; - -/** - * Returns a number of event listeners registered for the given event `type` - * on the given event `target`. - */ -function count(target, type) { - return observers(target, type).length; -} -exports.count = count; - -/** - * Registers listeners on the given event `target` from the given `listeners` - * dictionary. Iterates over the listeners and if property name matches name - * pattern `onEventType` and property is a function, then registers it as - * an `eventType` listener on `target`. - * - * @param {Object} target - * The type of event. - * @param {Object} listeners - * Dictionary of listeners. - */ -function setListeners(target, listeners) { - Object.keys(listeners || {}).forEach(key => { - let match = EVENT_TYPE_PATTERN.exec(key); - let type = match && match[1].toLowerCase(); - if (!type) return; - - let listener = listeners[key]; - if (typeof(listener) === 'function') - on(target, type, listener); - }); -} -exports.setListeners = setListeners; diff --git a/addon-sdk/source/lib/sdk/event/dom.js b/addon-sdk/source/lib/sdk/event/dom.js deleted file mode 100644 index da99dec7a..000000000 --- a/addon-sdk/source/lib/sdk/event/dom.js +++ /dev/null @@ -1,78 +0,0 @@ -/* 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"; - -module.metadata = { - "stability": "unstable" -}; - -const { Ci } = require("chrome"); - -var { emit } = require("./core"); -var { when: unload } = require("../system/unload"); -var listeners = new WeakMap(); - -const { Cu } = require("chrome"); -const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm"); -const { ThreadSafeChromeUtils } = Cu.import("resource://gre/modules/Services.jsm", {}); - -var getWindowFrom = x => - x instanceof Ci.nsIDOMWindow ? x : - x instanceof Ci.nsIDOMDocument ? x.defaultView : - x instanceof Ci.nsIDOMNode ? x.ownerDocument.defaultView : - null; - -function removeFromListeners() { - ShimWaiver.getProperty(this, "removeEventListener")("DOMWindowClose", removeFromListeners); - for (let cleaner of listeners.get(this)) - cleaner(); - - listeners.delete(this); -} - -// Simple utility function takes event target, event type and optional -// `options.capture` and returns node style event stream that emits "data" -// events every time event of that type occurs on the given `target`. -function open(target, type, options) { - let output = {}; - let capture = options && options.capture ? true : false; - let listener = (event) => emit(output, "data", event); - - // `open` is currently used only on DOM Window objects, however it was made - // to be used to any kind of `target` that supports `addEventListener`, - // therefore is safer get the `window` from the `target` instead assuming - // that `target` is the `window`. - let window = getWindowFrom(target); - - // If we're not able to get a `window` from `target`, there is something - // wrong. We cannot add listeners that can leak later, or results in - // "dead object" exception. - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1001833 - if (!window) - throw new Error("Unable to obtain the owner window from the target given."); - - let cleaners = listeners.get(window); - if (!cleaners) { - cleaners = []; - listeners.set(window, cleaners); - - // We need to remove from our map the `window` once is closed, to prevent - // memory leak - ShimWaiver.getProperty(window, "addEventListener")("DOMWindowClose", removeFromListeners); - } - - cleaners.push(() => ShimWaiver.getProperty(target, "removeEventListener")(type, listener, capture)); - ShimWaiver.getProperty(target, "addEventListener")(type, listener, capture); - - return output; -} - -unload(() => { - let keys = ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(listeners) - for (let window of keys) - removeFromListeners.call(window); -}); - -exports.open = open; diff --git a/addon-sdk/source/lib/sdk/event/target.js b/addon-sdk/source/lib/sdk/event/target.js deleted file mode 100644 index 3a1f5e5f0..000000000 --- a/addon-sdk/source/lib/sdk/event/target.js +++ /dev/null @@ -1,74 +0,0 @@ -/* 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'; - -module.metadata = { - "stability": "stable" -}; - -const { on, once, off, setListeners } = require('./core'); -const { method, chainable } = require('../lang/functional/core'); -const { Class } = require('../core/heritage'); - -/** - * `EventTarget` is an exemplar for creating an objects that can be used to - * add / remove event listeners on them. Events on these objects may be emitted - * via `emit` function exported by 'event/core' module. - */ -const EventTarget = Class({ - /** - * Method initializes `this` event source. It goes through properties of a - * given `options` and registers listeners for the ones that look like an - * event listeners. - */ - /** - * Method initializes `this` event source. It goes through properties of a - * given `options` and registers listeners for the ones that look like an - * event listeners. - */ - initialize: function initialize(options) { - setListeners(this, options); - }, - /** - * Registers an event `listener` that is called every time events of - * specified `type` are emitted. - * @param {String} type - * The type of event. - * @param {Function} listener - * The listener function that processes the event. - * @example - * worker.on('message', function (data) { - * console.log('data received: ' + data) - * }) - */ - on: chainable(method(on)), - /** - * Registers an event `listener` that is called once the next time an event - * of the specified `type` is emitted. - * @param {String} type - * The type of the event. - * @param {Function} listener - * The listener function that processes the event. - */ - once: chainable(method(once)), - /** - * Removes an event `listener` for the given event `type`. - * @param {String} type - * The type of event. - * @param {Function} listener - * The listener function that processes the event. - */ - removeListener: function removeListener(type, listener) { - // Note: We can't just wrap `off` in `method` as we do it for other methods - // cause skipping a second or third argument will behave very differently - // than intended. This way we make sure all arguments are passed and only - // one listener is removed at most. - off(this, type, listener); - return this; - }, - // but we can wrap `off` here, as the semantics are the same - off: chainable(method(off)) - -}); -exports.EventTarget = EventTarget; diff --git a/addon-sdk/source/lib/sdk/event/utils.js b/addon-sdk/source/lib/sdk/event/utils.js deleted file mode 100644 index f193b6785..000000000 --- a/addon-sdk/source/lib/sdk/event/utils.js +++ /dev/null @@ -1,328 +0,0 @@ -/* 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"; - -module.metadata = { - "stability": "unstable" -}; - -var { emit, on, once, off, EVENT_TYPE_PATTERN } = require("./core"); -const { Cu } = require("chrome"); - -// This module provides set of high order function for working with event -// streams (streams in a NodeJS style that dispatch data, end and error -// events). - -// Function takes a `target` object and returns set of implicit references -// (non property references) it keeps. This basically allows defining -// references between objects without storing the explicitly. See transform for -// more details. -var refs = (function() { - let refSets = new WeakMap(); - return function refs(target) { - if (!refSets.has(target)) refSets.set(target, new Set()); - return refSets.get(target); - }; -})(); - -function transform(input, f) { - let output = new Output(); - - // Since event listeners don't prevent `input` to be GC-ed we wanna presrve - // it until `output` can be GC-ed. There for we add implicit reference which - // is removed once `input` ends. - refs(output).add(input); - - const next = data => receive(output, data); - once(output, "start", () => start(input)); - on(input, "error", error => emit(output, "error", error)); - on(input, "end", function() { - refs(output).delete(input); - end(output); - }); - on(input, "data", data => f(data, next)); - return output; -} - -// High order event transformation function that takes `input` event channel -// and returns transformation containing only events on which `p` predicate -// returns `true`. -function filter(input, predicate) { - return transform(input, function(data, next) { - if (predicate(data)) - next(data); - }); -} -exports.filter = filter; - -// High order function that takes `input` and returns input of it's values -// mapped via given `f` function. -const map = (input, f) => transform(input, (data, next) => next(f(data))); -exports.map = map; - -// High order function that takes `input` stream of streams and merges them -// into single event stream. Like flatten but time based rather than order -// based. -function merge(inputs) { - let output = new Output(); - let open = 1; - let state = []; - output.state = state; - refs(output).add(inputs); - - function end(input) { - open = open - 1; - refs(output).delete(input); - if (open === 0) emit(output, "end"); - } - const error = e => emit(output, "error", e); - function forward(input) { - state.push(input); - open = open + 1; - on(input, "end", () => end(input)); - on(input, "error", error); - on(input, "data", data => emit(output, "data", data)); - } - - // If `inputs` is an array treat it as a stream. - if (Array.isArray(inputs)) { - inputs.forEach(forward); - end(inputs); - } - else { - on(inputs, "end", () => end(inputs)); - on(inputs, "error", error); - on(inputs, "data", forward); - } - - return output; -} -exports.merge = merge; - -const expand = (inputs, f) => merge(map(inputs, f)); -exports.expand = expand; - -const pipe = (from, to) => on(from, "*", emit.bind(emit, to)); -exports.pipe = pipe; - - -// Shim signal APIs so other modules can be used as is. -const receive = (input, message) => { - if (input[receive]) - input[receive](input, message); - else - emit(input, "data", message); - - // Ideally our input will extend Input and already provide a weak value - // getter. If not, opportunistically shim the weak value getter on - // other types passed as the input. - if (!("value" in input)) { - Object.defineProperty(input, "value", WeakValueGetterSetter); - } - input.value = message; -}; -receive.toString = () => "@@receive"; -exports.receive = receive; -exports.send = receive; - -const end = input => { - if (input[end]) - input[end](input); - else - emit(input, "end", input); -}; -end.toString = () => "@@end"; -exports.end = end; - -const stop = input => { - if (input[stop]) - input[stop](input); - else - emit(input, "stop", input); -}; -stop.toString = () => "@@stop"; -exports.stop = stop; - -const start = input => { - if (input[start]) - input[start](input); - else - emit(input, "start", input); -}; -start.toString = () => "@@start"; -exports.start = start; - -const lift = (step, ...inputs) => { - let args = null; - let opened = inputs.length; - let started = false; - const output = new Output(); - const init = () => { - args = [...inputs.map(input => input.value)]; - output.value = step(...args); - }; - - inputs.forEach((input, index) => { - on(input, "data", data => { - args[index] = data; - receive(output, step(...args)); - }); - on(input, "end", () => { - opened = opened - 1; - if (opened <= 0) - end(output); - }); - }); - - once(output, "start", () => { - inputs.forEach(start); - init(); - }); - - init(); - - return output; -}; -exports.lift = lift; - -const merges = inputs => { - let opened = inputs.length; - let output = new Output(); - output.value = inputs[0].value; - inputs.forEach((input, index) => { - on(input, "data", data => receive(output, data)); - on(input, "end", () => { - opened = opened - 1; - if (opened <= 0) - end(output); - }); - }); - - once(output, "start", () => { - inputs.forEach(start); - output.value = inputs[0].value; - }); - - return output; -}; -exports.merges = merges; - -const foldp = (step, initial, input) => { - let output = map(input, x => step(output.value, x)); - output.value = initial; - return output; -}; -exports.foldp = foldp; - -const keepIf = (p, base, input) => { - let output = filter(input, p); - output.value = base; - return output; -}; -exports.keepIf = keepIf; - -function Input() {} -Input.start = input => emit(input, "start", input); -Input.prototype.start = Input.start; - -Input.end = input => { - emit(input, "end", input); - stop(input); -}; -Input.prototype[end] = Input.end; - -// The event channel system caches the last event seen as input.value. -// Unfortunately, if the last event is a DOM object this is a great way -// leak windows. Mitigate this by storing input.value using a weak -// reference. This allows the system to work for normal event processing -// while also allowing the objects to be reclaimed. It means, however, -// input.value cannot be accessed long after the event was dispatched. -const WeakValueGetterSetter = { - get: function() { - return this._weakValue ? this._weakValue.get() : this._simpleValue - }, - set: function(v) { - if (v && typeof v === "object") { - try { - // Try to set a weak reference. This can throw for some values. - // For example, if the value is a native object that does not - // implement nsISupportsWeakReference. - this._weakValue = Cu.getWeakReference(v) - this._simpleValue = undefined; - return; - } catch (e) { - // Do nothing. Fall through to setting _simpleValue below. - } - } - this._simpleValue = v; - this._weakValue = undefined; - }, -} -Object.defineProperty(Input.prototype, "value", WeakValueGetterSetter); - -exports.Input = Input; - -// Define an Output type with a weak value getter for the transformation -// functions that produce new channels. -function Output() { } -Object.defineProperty(Output.prototype, "value", WeakValueGetterSetter); -exports.Output = Output; - -const $source = "@@source"; -const $outputs = "@@outputs"; -exports.outputs = $outputs; - -// NOTE: Passing DOM objects through a Reactor can cause them to leak -// when they get cached in this.value. We cannot use a weak reference -// in this case because the Reactor design expects to always have both the -// past and present value. If we allow past values to be collected the -// system breaks. - -function Reactor(options={}) { - const {onStep, onStart, onEnd} = options; - if (onStep) - this.onStep = onStep; - if (onStart) - this.onStart = onStart; - if (onEnd) - this.onEnd = onEnd; -} -Reactor.prototype.onStep = _ => void(0); -Reactor.prototype.onStart = _ => void(0); -Reactor.prototype.onEnd = _ => void(0); -Reactor.prototype.onNext = function(present, past) { - this.value = present; - this.onStep(present, past); -}; -Reactor.prototype.run = function(input) { - on(input, "data", message => this.onNext(message, input.value)); - on(input, "end", () => this.onEnd(input.value)); - start(input); - this.value = input.value; - this.onStart(input.value); -}; -exports.Reactor = Reactor; - -/** - * Takes an object used as options with potential keys like 'onMessage', - * used to be called `require('sdk/event/core').setListeners` on. - * This strips all keys that would trigger a listener to be set. - * - * @params {Object} object - * @return {Object} - */ - -function stripListeners (object) { - return Object.keys(object || {}).reduce((agg, key) => { - if (!EVENT_TYPE_PATTERN.test(key)) - agg[key] = object[key]; - return agg; - }, {}); -} -exports.stripListeners = stripListeners; - -const when = (target, type) => new Promise(resolve => { - once(target, type, resolve); -}); -exports.when = when; |