addon-sdk/source/lib/sdk/event/utils.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4 "use strict";
michael@0 5
michael@0 6 module.metadata = {
michael@0 7 "stability": "unstable"
michael@0 8 };
michael@0 9
michael@0 10 let { emit, on, once, off, EVENT_TYPE_PATTERN } = require("./core");
michael@0 11
michael@0 12 // This module provides set of high order function for working with event
michael@0 13 // streams (streams in a NodeJS style that dispatch data, end and error
michael@0 14 // events).
michael@0 15
michael@0 16 // Function takes a `target` object and returns set of implicit references
michael@0 17 // (non property references) it keeps. This basically allows defining
michael@0 18 // references between objects without storing the explicitly. See transform for
michael@0 19 // more details.
michael@0 20 let refs = (function() {
michael@0 21 let refSets = new WeakMap();
michael@0 22 return function refs(target) {
michael@0 23 if (!refSets.has(target)) refSets.set(target, new Set());
michael@0 24 return refSets.get(target);
michael@0 25 };
michael@0 26 })();
michael@0 27
michael@0 28 function transform(input, f) {
michael@0 29 let output = {};
michael@0 30
michael@0 31 // Since event listeners don't prevent `input` to be GC-ed we wanna presrve
michael@0 32 // it until `output` can be GC-ed. There for we add implicit reference which
michael@0 33 // is removed once `input` ends.
michael@0 34 refs(output).add(input);
michael@0 35
michael@0 36 const next = data => receive(output, data);
michael@0 37 once(output, "start", () => start(input));
michael@0 38 on(input, "error", error => emit(output, "error", error));
michael@0 39 on(input, "end", function() {
michael@0 40 refs(output).delete(input);
michael@0 41 end(output);
michael@0 42 });
michael@0 43 on(input, "data", data => f(data, next));
michael@0 44 return output;
michael@0 45 }
michael@0 46
michael@0 47 // High order event transformation function that takes `input` event channel
michael@0 48 // and returns transformation containing only events on which `p` predicate
michael@0 49 // returns `true`.
michael@0 50 function filter(input, predicate) {
michael@0 51 return transform(input, function(data, next) {
michael@0 52 if (predicate(data))
michael@0 53 next(data);
michael@0 54 });
michael@0 55 }
michael@0 56 exports.filter = filter;
michael@0 57
michael@0 58 // High order function that takes `input` and returns input of it's values
michael@0 59 // mapped via given `f` function.
michael@0 60 const map = (input, f) => transform(input, (data, next) => next(f(data)));
michael@0 61 exports.map = map;
michael@0 62
michael@0 63 // High order function that takes `input` stream of streams and merges them
michael@0 64 // into single event stream. Like flatten but time based rather than order
michael@0 65 // based.
michael@0 66 function merge(inputs) {
michael@0 67 let output = {};
michael@0 68 let open = 1;
michael@0 69 let state = [];
michael@0 70 output.state = state;
michael@0 71 refs(output).add(inputs);
michael@0 72
michael@0 73 function end(input) {
michael@0 74 open = open - 1;
michael@0 75 refs(output).delete(input);
michael@0 76 if (open === 0) emit(output, "end");
michael@0 77 }
michael@0 78 const error = e => emit(output, "error", e);
michael@0 79 function forward(input) {
michael@0 80 state.push(input);
michael@0 81 open = open + 1;
michael@0 82 on(input, "end", () => end(input));
michael@0 83 on(input, "error", error);
michael@0 84 on(input, "data", data => emit(output, "data", data));
michael@0 85 }
michael@0 86
michael@0 87 // If `inputs` is an array treat it as a stream.
michael@0 88 if (Array.isArray(inputs)) {
michael@0 89 inputs.forEach(forward);
michael@0 90 end(inputs);
michael@0 91 }
michael@0 92 else {
michael@0 93 on(inputs, "end", () => end(inputs));
michael@0 94 on(inputs, "error", error);
michael@0 95 on(inputs, "data", forward);
michael@0 96 }
michael@0 97
michael@0 98 return output;
michael@0 99 }
michael@0 100 exports.merge = merge;
michael@0 101
michael@0 102 const expand = (inputs, f) => merge(map(inputs, f));
michael@0 103 exports.expand = expand;
michael@0 104
michael@0 105 const pipe = (from, to) => on(from, "*", emit.bind(emit, to));
michael@0 106 exports.pipe = pipe;
michael@0 107
michael@0 108
michael@0 109 // Shim signal APIs so other modules can be used as is.
michael@0 110
michael@0 111 const receive = (input, message) => {
michael@0 112 if (input[receive])
michael@0 113 input[receive](input, message);
michael@0 114 else
michael@0 115 emit(input, "data", message);
michael@0 116
michael@0 117 input.value = message;
michael@0 118 };
michael@0 119 receive.toString = () => "@@receive";
michael@0 120 exports.receive = receive;
michael@0 121 exports.send = receive;
michael@0 122
michael@0 123 const end = input => {
michael@0 124 if (input[end])
michael@0 125 input[end](input);
michael@0 126 else
michael@0 127 emit(input, "end", input);
michael@0 128 };
michael@0 129 end.toString = () => "@@end";
michael@0 130 exports.end = end;
michael@0 131
michael@0 132 const stop = input => {
michael@0 133 if (input[stop])
michael@0 134 input[stop](input);
michael@0 135 else
michael@0 136 emit(input, "stop", input);
michael@0 137 };
michael@0 138 stop.toString = () => "@@stop";
michael@0 139 exports.stop = stop;
michael@0 140
michael@0 141 const start = input => {
michael@0 142 if (input[start])
michael@0 143 input[start](input);
michael@0 144 else
michael@0 145 emit(input, "start", input);
michael@0 146 };
michael@0 147 start.toString = () => "@@start";
michael@0 148 exports.start = start;
michael@0 149
michael@0 150 const lift = (step, ...inputs) => {
michael@0 151 let args = null;
michael@0 152 let opened = inputs.length;
michael@0 153 let started = false;
michael@0 154 const output = {};
michael@0 155 const init = () => {
michael@0 156 args = [...inputs.map(input => input.value)];
michael@0 157 output.value = step(...args);
michael@0 158 };
michael@0 159
michael@0 160 inputs.forEach((input, index) => {
michael@0 161 on(input, "data", data => {
michael@0 162 args[index] = data;
michael@0 163 receive(output, step(...args));
michael@0 164 });
michael@0 165 on(input, "end", () => {
michael@0 166 opened = opened - 1;
michael@0 167 if (opened <= 0)
michael@0 168 end(output);
michael@0 169 });
michael@0 170 });
michael@0 171
michael@0 172 once(output, "start", () => {
michael@0 173 inputs.forEach(start);
michael@0 174 init();
michael@0 175 });
michael@0 176
michael@0 177 init();
michael@0 178
michael@0 179 return output;
michael@0 180 };
michael@0 181 exports.lift = lift;
michael@0 182
michael@0 183 const merges = inputs => {
michael@0 184 let opened = inputs.length;
michael@0 185 let output = { value: inputs[0].value };
michael@0 186 inputs.forEach((input, index) => {
michael@0 187 on(input, "data", data => receive(output, data));
michael@0 188 on(input, "end", () => {
michael@0 189 opened = opened - 1;
michael@0 190 if (opened <= 0)
michael@0 191 end(output);
michael@0 192 });
michael@0 193 });
michael@0 194
michael@0 195 once(output, "start", () => {
michael@0 196 inputs.forEach(start);
michael@0 197 output.value = inputs[0].value;
michael@0 198 });
michael@0 199
michael@0 200 return output;
michael@0 201 };
michael@0 202 exports.merges = merges;
michael@0 203
michael@0 204 const foldp = (step, initial, input) => {
michael@0 205 let output = map(input, x => step(output.value, x));
michael@0 206 output.value = initial;
michael@0 207 return output;
michael@0 208 };
michael@0 209 exports.foldp = foldp;
michael@0 210
michael@0 211 const keepIf = (p, base, input) => {
michael@0 212 let output = filter(input, p);
michael@0 213 output.value = base;
michael@0 214 return output;
michael@0 215 };
michael@0 216 exports.keepIf = keepIf;
michael@0 217
michael@0 218 function Input() {}
michael@0 219 Input.start = input => emit(input, "start", input);
michael@0 220 Input.prototype.start = Input.start;
michael@0 221
michael@0 222 Input.end = input => {
michael@0 223 emit(input, "end", input);
michael@0 224 stop(input);
michael@0 225 };
michael@0 226 Input.prototype[end] = Input.end;
michael@0 227
michael@0 228 exports.Input = Input;
michael@0 229
michael@0 230 const $source = "@@source";
michael@0 231 const $outputs = "@@outputs";
michael@0 232 exports.outputs = $outputs;
michael@0 233
michael@0 234 function Reactor(options={}) {
michael@0 235 const {onStep, onStart, onEnd} = options;
michael@0 236 if (onStep)
michael@0 237 this.onStep = onStep;
michael@0 238 if (onStart)
michael@0 239 this.onStart = onStart;
michael@0 240 if (onEnd)
michael@0 241 this.onEnd = onEnd;
michael@0 242 }
michael@0 243 Reactor.prototype.onStep = _ => void(0);
michael@0 244 Reactor.prototype.onStart = _ => void(0);
michael@0 245 Reactor.prototype.onEnd = _ => void(0);
michael@0 246 Reactor.prototype.onNext = function(present, past) {
michael@0 247 this.value = present;
michael@0 248 this.onStep(present, past);
michael@0 249 };
michael@0 250 Reactor.prototype.run = function(input) {
michael@0 251 on(input, "data", message => this.onNext(message, input.value));
michael@0 252 on(input, "end", () => this.onEnd(input.value));
michael@0 253 start(input);
michael@0 254 this.value = input.value;
michael@0 255 this.onStart(input.value);
michael@0 256 };
michael@0 257 exports.Reactor = Reactor;
michael@0 258
michael@0 259 /**
michael@0 260 * Takes an object used as options with potential keys like 'onMessage',
michael@0 261 * used to be called `require('sdk/event/core').setListeners` on.
michael@0 262 * This strips all keys that would trigger a listener to be set.
michael@0 263 *
michael@0 264 * @params {Object} object
michael@0 265 * @return {Object}
michael@0 266 */
michael@0 267
michael@0 268 function stripListeners (object) {
michael@0 269 return Object.keys(object || {}).reduce((agg, key) => {
michael@0 270 if (!EVENT_TYPE_PATTERN.test(key))
michael@0 271 agg[key] = object[key];
michael@0 272 return agg;
michael@0 273 }, {});
michael@0 274 }
michael@0 275 exports.stripListeners = stripListeners;

mercurial